Let’s Build a Shopify e-commerce App with NodeJS

Chris D’Ascoli
8 min readApr 19, 2018

ugh, you’ve probably been in this situation before… a job post you’re interested in requests ‘e-commerce experience’, or your brand new job (congrats!) uses an e-commerce platform, or maybe through the developer grapevine you’ve heard <insert any e-commerce platform here> is so annoying to work with stay away!(I can’t be the only one who’s heard that).

Well, I’m here to tell you that happens a lot (it happend to me) so don’t sweat it. When I first started at wynd I too had to learn how Shopify worked, granted all the code and setup was already done, but in order to understand our codebase and be able to debug issues related to our store I took it upon my myself to dig in.

I dont know about you, but I learn 70/30 through doing vs. reading (although I do love reading me some Medium articles at 8am sipping my coffee before I start the day, it’s GOLORIOUS). To me the best way to really learn and get a feel for something is to dig in a bit and play with the code, so I put this tutorial together for those like me.

Prerequisites

we’ll be using the following so you’ll need at least some knowledge of them:

Node: love me some Node.js.

ngrok: needed to tunnel the external world to our little old localhost.

Express: our server.

Shopify Partners Account: don’t worry I walk you through this setup below.

Shopify Partners

First, we need to create an account and setup our shop!

Head on over here and click Join Now..

enter your email..

..then your name and password…

..then more info about your store. I used generic ‘test’ for pretty much everything.

great! now click ‘Development stores’ in the left hand side menu..

then click ‘Create store’..

From here fill out the login info and store details section that appears next and click save.

head back over to Shopify Partners click Apps in the left hand side menu and click ‘Create app’. It looks almost exactly the same as the picture above for ‘Create store’.

next you’ll see the below..

Enter your app name (whatever you want), but for the URL this is where our pre-req of ngrok comes into play. Fire up ngrok in whatever directory you made to house this app and copy/paste the https url ngrok gives you into the App URL box followed by a /shopify. So it should look something like this..

https://mumbo-jumbo.ngrok.io/shopify

then click Create app.

Next, click app info in the dashboard and under ‘Whitelisted redirection URL(s)’ you should see the URL you just supplied. Now, add a /callback to the end of it. So it should look like this…

https://mumbo-jumbo.ngrok.io/shopify/callback

This is the URL that they will be redirected to once they give permission to your app. DONT FORGET TO CLICK SAVE IN THE TOP RIGHT!

App Setup

Finally, thats over. Now let’s setup our app!

run the below (or npm if you prefer)..

yarn init -y
// the -y flag uses the default settings

next run:

yarn add express dotenv cookie nonce axiosyarn add nodemon --dev

express: our server.

dotenv: helps us store and utilize environment variables.

cookie: a simple cookie parse.

nonce: according to the Wikipedia, a nonce is an arbitrary number used only once in a cryptographic communication. This package contain helper functions for generating nonces.

axios: my request method of choice, but feel free to use whatever you prefer.

nodemon: auto restarts our server after we make changes… cuz we lazy.

add the following script to your package.json file:

"scripts": {
"start": "nodemon index.js"
},

let’s add a file for our environment variables..

touch .env

then add your Shopify public and secret keys (found in the app info section where we whitelisted our redirection url at the bottom).

add a index.js file..

touch index.js

and copy/paste the following code. Well review each section in chunks below.

Let’s start here..

///////////// Initial Setup /////////////// our env file needed to import our variables.
const dotenv = require('dotenv').config();
// our express server.
const express = require('express');
// nodes built in cryptography helper.
const crypto = require('crypto');
// simple cookie parser we installed earlier.
const cookie = require('cookie');
// help us to generate random strings.
const nonce = require('nonce')();
// nodes built in querystring helps us build url query strings.
const querystring = require('querystring');
// our request library.
const axios = require('axios');
// our shopify keys (easier than writing process.env everytime).
const shopifyApiPublicKey = process.env.SHOPIFY_API_PUBLIC_KEY;
const shopifyApiSecretKey = process.env.SHOPIFY_API_SECRET_KEY;
// the scope of the store we want (permissions).
const scopes = 'write_products';
// the ngrok forwarding address.
const appUrl = 'https://509cee43.ngrok.io';
const app = express();
const PORT = 3000
app.get('/', (req, res) => {
res.send('Ello Govna')
});

now that we understand what’s being required and the global variables we have set let’s get into the helper functions. The names are pretty self-explanatory but..

///////////// Helper Functions /////////////// returns our redirect uri.const buildRedirectUri = () => `${appUrl}/shopify/callback`;// returns the url we redirect the client to which will request permission from the user to access their store.const buildInstallUrl = (shop, state, redirectUri) => `https://${shop}/admin/oauth/authorize?client_id=${shopifyApiPublicKey}&scope=${scopes}&state=${state}&redirect_uri=${redirectUri}`;// returns the url where we need to post to in order to get our access token.const buildAccessTokenRequestUrl = (shop) => `https://${shop}/admin/oauth/access_token`;// returns the url we get from to receive shop data, but this could be changed to products or orders etc.const buildShopDataRequestUrl = (shop) => `https://${shop}/admin/shop.json`;// returns our encrypted hash to compare to the one we receive from shopify.const generateEncryptedHash = (params) => crypto.createHmac('sha256', shopifyApiSecretKey).update(params).digest('hex');// request to get our access token.const fetchAccessToken = async (shop, data) => await axios(buildAccessTokenRequestUrl(shop), {
method: 'POST',
data
});
// request to get the shop data.const fetchShopData = async (shop, accessToken) => await axios(buildShopDataRequestUrl(shop), {
method: 'GET',
headers: {
'X-Shopify-Access-Token': accessToken
}
});

moving onto our route handler logic..

///////////// Route Handlers /////////////
app.get('/shopify', (req, res) => {
// the shop will be in a query string attached to the url. it would look like this https://whatever.com/shopify?shop=<The Shop Url>.const shop = req.query.shop;// if the shop param isn't present return.if (!shop) { return res.status(400).send('no shop')}// we create a state variable equal to a random string.const state = nonce();// we use helper methods to create the url we are going to redirect the client to. const installShopUrl = buildInstallUrl(shop, state, buildRedirectUri())// we add a cookie to their browser with the random state string typically this would be encrypted in production. res.cookie('state', state)// we redirect them to the install url where they will approve our scope and give us permission to access their data. res.redirect(installShopUrl);});
app.get('/shopify/callback', async (req, res) => {// once they approve our access (in the previous route) they are then sent to our callback route which provides the shop, code, state, hmac, and timestamp query params.. we chose to only destructure a few of them.const { shop, code, state } = req.query;// we parse the cookie their client sent back to us and pull out the state.const stateCookie = cookie.parse(req.headers.cookie).state;// we ensure the request is valid by comparing the two.if (state !== stateCookie) { return res.status(403).send('Cannot be verified')}// we now pull out the hmac from req.query and create a new object (params) that will contain everything (shop, code, state, timestamp) but the hmac key/value pair.const { hmac, ...params } = req.query// we then transform the params into a query string.const queryParams = querystring.stringify(params)// we then generate an encrypted hash with said queryParams.const hash = generateEncryptedHash(queryParams)// we compare the hmac shopify sent to us with the one we just generated, if they aren't the same we know it's an invalid request.if (hash !== hmac) { return res.status(400).send('HMAC validation failed')}// we setup a try/catch block for our requests.try {// we create our payload that will be included in our first request to get the access token. const data = {
client_id: shopifyApiPublicKey,
client_secret: shopifyApiSecretKey,
code
};
// we await the response from our helper function. const tokenResponse = await fetchAccessToken(shop, data)// we destructure the response and pull out the access token. const { access_token } = tokenResponse.data// once we have our access token we can send another request to get the information we desire (this could be any number of different endpoints shopify has but we chose the shop data for this example). const shopData = await fetchShopData(shop, access_token)

// if all went well you should see the shop data in the browser from here you would obviously do whatever you need to this is only for example purposes to show it worked.
res.send(shopData.data.shop)} catch(err) { console.log(err)// if it errors out we log it and return an error. res.status(500).send('something went wrong')
}
});

to test it all worked open a browser tab and head on over to the below.

<YOUR APP URL>/shopify?shop=<YOUR STORE URL>// APP URL: What ngrok is forwarding to so something like this https://numbersAndStuff.ngrok.io// STORE URL: The store you created in the first steps URL.  So, something like this (no https:// needed):
i-named-my-store-this.myshopify.com

You will be taken to the approval/permissions page where you would approve our app to have access to whatever scope(s) have been requested. Once you do you’ll be redirected and should see the shop data in the browser (since that’s what we’re sending back at the end).

That’s all folks! Code can be found here if you’re interested.

References:

Shopify API Documentation

Shopify App Tutorials

--

--