Dissecting the #Shopify #NodeJs Bridge
The Shopify NodeJs bridge by MobileFront is a NodeJS API that runs on Heroku, and it allows easy access to your Shopify Data via a JSON based API.
Here’s the core of the code in index.js
(function () {
‘use strict’;
var express = require(‘express’),
sprintf = require(‘sprintf’),
crypto = require(‘crypto’),
fs = require(‘fs’),
toSlugCase = require(‘to-slug-case’),
httpRequest = require(‘./http.request’),
app = express(),
shopifyAPI = require(‘shopify-node-api’),
port = process.env.PORT || 3000,
config = JSON.parse(fs.readFileSync(‘config.json’, ‘utf8’)),
shopify = new shopifyAPI(config);
// Shopify App installapp.get(‘/install’, function (req, res) {
//res.send(‘This is ShopToApp installation page’);
var shop = req.query.shop,
apiKey = req.query.apiKey,
apiSecret = req.query.apiSecret,
Shopify,
authURL;if (config.access_token) {
// reject, we should not allow installing the app more than once
res.status(400).send({
status: ‘failed’,
message: ‘The app is already installed’
});} else if (!shop || !apiKey || !apiSecret) {
res.status(400).send({
status: ‘failed’,
message: ‘Missing required parameters’
});
} else {
config.shop = shop;
config.shopify_api_key = apiKey;
config.shopify_shared_secret = apiSecret;
config.redirect_uri = req.headers[“x-forwarded-proto”] + ‘://’ + req.get(‘host’) + ‘/auth’;
config.nonce = crypto.randomBytes(64).toString(‘hex’);Shopify = new shopifyAPI(config);
authURL = Shopify.buildAuthURL();res.redirect(authURL);
}});
app.get(‘/auth’, function (req, res) {
//params: code, hmac, timestamp, state, shop
var requestParams = req.query,
shop,
Shopify;if (req.query.shop) {
shop = req.query.shop.replace(‘.myshopify.com’, ”);
config.shop = shop;
Shopify = new shopifyAPI(config);Shopify.exchange_temporary_token(requestParams, function (err, data) {
if (!err) {config.access_token = data.access_token;
shopify = new shopifyAPI(config);res.status(200).send(‘You have successfully installed the MobileFront Bridge! Your Access Token is: ‘ + config.access_token + ‘. Keep it safe.’);
} else {
res.status(400).send({
status: ‘failed’,
message: ‘Failed to install the app. Reason: ‘ + err + ‘, shop: ‘ + config.shop
});
}
});
} else {
res.status(400).send({
status: ‘failed’,
message: ‘Invalid parameters’
});
}});
// API
// =======================
// CORS ==================
// =======================
app.use(‘/’, function (req, res, next) {
res.header(‘Access-Control-Allow-Origin’, ‘*’);
res.header(‘Access-Control-Allow-Methods’, ‘GET,PUT,POST,DELETE,OPTIONS’);
res.header(‘Access-Control-Allow-Headers’, ‘Content-Type, Authorization, Content-Length, X-Requested-With’);
// intercept OPTIONS method
if (‘OPTIONS’ === req.method) {
res.sendStatus(200);
} else {
next();
}
});app.use(‘/api’, function (req, res, next) {
if (!config.shop || !config.access_token) {
res.status(400).send({
status: ‘failed’,
message: ‘Bridge was not installed’
});
} else {
req.shopify = shopify;
next();
}});
app.get(‘/api/getShopData’, function (req, res) {
var shopify = req.shopify;shopify.get(‘/admin/shop.json’, function (err, data, headers) {
if (!err) {
res.json(data);
} else {
res.status(400).json({
status: ‘failed’,
message: err
});
}
});
});app.get(‘/api/getSmartCollections’, function (req, res) {
var shopify = req.shopify;shopify.get(‘/admin/smart_collections.json’, function (err, data, headers) {
if (!err) {
res.json(data);
} else {
res.status(400).json({
status: ‘failed’,
message: err
});
}
});
});app.get(‘/api/getCollection’, function (req, res) {
var shopify = req.shopify,
collectionId = req.query[‘collectionId’];if (collectionId) {
// first look in smart collections
shopify.get(‘/admin/smart_collections/’ + collectionId + ‘.json’, function (err, data, headers) {
if (!err) {
res.json(data.smart_collection);
} else {
// if not found look in custom collections
shopify.get(‘/admin/custom_collections/’ + collectionId + ‘.json’, function (err, data, headers) {
if (!err) {
res.json(data.custom_collection);
} else {
res.status(400).json({
status: ‘failed’,
message: err
});
}
});
}
});
} else {
res.status(400).json({
status: ‘failed’,
message: ‘Missing or empty collectionId parameter’
});
}
});
app.get(‘/api/getProductsForCollection/’, function (req, res) {
var shopify = req.shopify,
query = req.query,
params = Object.keys(query).map(function (key) {
var keyValue = key + ‘=’;
if (key === ‘title’) {
keyValue += encodeURIComponent(query[key]);
} else {
keyValue += query[key];
}
return keyValue;
});shopify.get(‘/admin/products.json?’ + params.join(‘&’), function (err, data, headers) {
if (!err) {
res.json(data);
} else {
res.status(400).json({
status: ‘failed’,
message: err
});
}
});
});app.get(‘/api/getNavigationLinks’, function (req, res) {
var SHOPIFY_STORE_URL = ‘https://’ + config.shop + ‘.myshopify.com’,
url = SHOPIFY_STORE_URL + ‘/apps/proxy/categories.json’;httpRequest.get(url, null, null, function (result) {
res.json(result);
}, function (error) {
console.log(‘/api/getNavigationLinks: ‘ + JSON.stringify(error));
res.status(400).json({
status: ‘failed’,
message: ‘Failed to retrieve navigation links. Is your shop password protected?’
});
});
});// the route below is used by Shopify when processing proxy requests
app.get(‘/proxy/categories.json’, function (req, res) {
var parentCategory = req.query.parent || ‘main-menu’,
output;//output = ‘{% layout none %}{% capture items %}{% for link in linklists.’ + parentCategory + ‘.links %}{% if link.type == “collection_link” %}{ “title”: “{{link.title}}”, “handle”: “{{link.title | handleize}}”, “collection”: “{{link.object.id}}”},{% endif %}{% endfor %}{% endcapture items %}{% assign length = items | size %}{% if length != 0 %}{% assign sliceLength = length | minus: 1 %}{% assign items = items | slice : 0, sliceLength %}{% endif %}[{{items}}]’;
output = ‘{% layout none %}{% capture items %}{% for linklist in linklists %}{% for link in linklist.links %}{% if link.type == “collection_link” %}{ “title”: “{{link.title}}”, “handle”: “{{link.title | handleize}}”, “collection”: “{{link.object.id}}”, “parent”: “{{linklist.handle}}”},{% endif %}{% endfor %}{% endfor %}{% endcapture items %}{% assign length = items | size %}{% if length != 0 %}{% assign sliceLength = length | minus: 1 %}{% assign items = items | slice : 0, sliceLength %}{% endif %}[{{items}}]’;res.set(‘Content-Type’, ‘application/liquid’);
res.send(output);
});app.listen(port);
})();
I have this running on a development shop, with one product, so here is what it returns for it’s various endpoints;
http://hidden-bastion-72179.herokuapp.com/api/getNavigationLinks
[
{
“title”: “Gym”,
“handle”: “gym”,
“collection”: “422347917”,
“parent”: “main-menu”
}
]
http://hidden-bastion-72179.herokuapp.com/api/getShopData
{
“shop”: {
“id”: 18854285,
“name”: “webtropy”,
“email”: “fiach.reid@gmail.com”,
“domain”: “webtropy.myshopify.com”,
“created_at”: “2017-03-29T05:04:07-04:00”,
“province”: “”,
“country”: “GB”,
“address1”: “10 Nualamont Drive, 10 Nualamont Drive”,
“zip”: “BT48 9PH”,
“city”: “Derry”,
“source”: null,
“phone”: “”,
“updated_at”: “2017-03-29T05:25:02-04:00”,
“customer_email”: null,
“latitude”: null,
“longitude”: null,
“primary_location_id”: 40035597,
“primary_locale”: “en”,
“address2”: null,
“country_code”: “GB”,
“country_name”: “United Kingdom”,
“currency”: “GBP”,
“timezone”: “(GMT-05:00) Eastern Time (US & Canada)”,
“iana_timezone”: “America\/New_York”,
“shop_owner”: “Fiach Reid”,
“money_format”: “\u00a3{{amount}}”,
“money_with_currency_format”: “\u00a3{{amount}} GBP”,
“weight_unit”: “lb”,
“province_code”: null,
“taxes_included”: true,
“tax_shipping”: null,
“county_taxes”: true,
“plan_display_name”: “affiliate”,
“plan_name”: “affiliate”,
“has_discounts”: false,
“has_gift_cards”: false,
“myshopify_domain”: “webtropy.myshopify.com”,
“google_apps_domain”: null,
“google_apps_login_enabled”: null,
“money_in_emails_format”: “\u00a3{{amount}}”,
“money_with_currency_in_emails_format”: “\u00a3{{amount}} GBP”,
“eligible_for_payments”: true,
“requires_extra_payments_agreement”: false,
“password_enabled”: false,
“has_storefront”: true,
“eligible_for_card_reader_giveaway”: false,
“finances”: true,
“setup_required”: false,
“force_ssl”: true
}
}
http://hidden-bastion-72179.herokuapp.com/api/getSmartCollections
{
“smart_collections”: [
{
“id”: 422347917,
“handle”: “gym”,
“title”: “Gym”,
“updated_at”: “2017-03-29T05:40:56-04:00”,
“body_html”: “”,
“published_at”: “2017-03-29T05:38:00-04:00”,
“sort_order”: “best-selling”,
“template_suffix”: null,
“published_scope”: “global”,
“disjunctive”: false,
“rules”: [
{
“column”: “tag”,
“relation”: “equals”,
“condition”: “Gym”
}
]
}
]
}
http://hidden-bastion-72179.herokuapp.com/api/getCollection?collectionId=422347917
{
“id”: 422347917,
“handle”: “gym”,
“title”: “Gym”,
“updated_at”: “2017-03-29T05:40:56-04:00”,
“body_html”: “”,
“published_at”: “2017-03-29T05:38:00-04:00”,
“sort_order”: “best-selling”,
“template_suffix”: null,
“products_count”: 1,
“published_scope”: “global”,
“disjunctive”: false,
“rules”: [
{
“column”: “tag”,
“relation”: “equals”,
“condition”: “Gym”
}
]
}
http://hidden-bastion-72179.herokuapp.com/api/getProductsForCollection
{
“products”: [
{
“id”: 9487359757,
“title”: “Dumbells”,
“body_html”: “”,
“vendor”: “webtropy”,
“product_type”: “”,
“created_at”: “2017-03-29T05:40:56-04:00”,
“handle”: “dumbells”,
“updated_at”: “2017-03-29T05:40:58-04:00”,
“published_at”: “2017-03-29T05:39:00-04:00”,
“template_suffix”: null,
“published_scope”: “global”,
“tags”: “gym”,
“variants”: [
{
“id”: 35073137037,
“product_id”: 9487359757,
“title”: “Default Title”,
“price”: “100.00”,
“sku”: “”,
“position”: 1,
“grams”: 0,
“inventory_policy”: “deny”,
“compare_at_price”: null,
“fulfillment_service”: “manual”,
“inventory_management”: null,
“option1”: “Default Title”,
“option2”: null,
“option3”: null,
“created_at”: “2017-03-29T05:40:56-04:00”,
“updated_at”: “2017-03-29T05:40:56-04:00”,
“taxable”: true,
“barcode”: “”,
“image_id”: null,
“inventory_quantity”: 1,
“weight”: 0,
“weight_unit”: “lb”,
“old_inventory_quantity”: 1,
“requires_shipping”: true
}
],
“options”: [
{
“id”: 11375442061,
“product_id”: 9487359757,
“name”: “Title”,
“position”: 1,
“values”: [
“Default Title”
]
}
],
“images”: [
{
“id”: 22923954125,
“product_id”: 9487359757,
“position”: 1,
“created_at”: “2017-03-29T05:40:58-04:00”,
“updated_at”: “2017-03-29T05:40:58-04:00”,
“src”: “https:\/\/cdn.shopify.com\/s\/files\/1\/1885\/4285\/products\/exercise-equipment-product-image.jpg?v=1490780458”,
“variant_ids”: []
}
],
“image”: {
“id”: 22923954125,
“product_id”: 9487359757,
“position”: 1,
“created_at”: “2017-03-29T05:40:58-04:00”,
“updated_at”: “2017-03-29T05:40:58-04:00”,
“src”: “https:\/\/cdn.shopify.com\/s\/files\/1\/1885\/4285\/products\/exercise-equipment-product-image.jpg?v=1490780458”,
“variant_ids”: []
}
}
]
}