Building a Shopify application with Node.js and Express

In this tutorial you are going to build a Node.js + Express application that connects to Shopify. Your application will authenticate with a shop, request a permanent access token, and make an API call using that access token.

Step 1: Expose your local development environment to the internet

As part of the authentication process, Shopify will redirect the user from the App authorization prompt back to your application. This requires your application to have a public HTTPS address (localhost:3000 is not a public address). The simplest solution is to use a service like ngrok to create a secure tunnel from the public internet to your local machine.

  1. Download ngrok
  2. Start an ngrok tunnel on port 3000.

    # if ngrok is in your downloads folder:
    $ cd ~/Downloads/ngrok
    # start an HTTP tunnel on port 3000
    $ ngrok http 3000
    
  3. Copy the generated Forwarding address (e.g https://81c14560.ngrok.io) - you will provide it to Shopify as your App URL

Tip

Your Forwarding address will change every time you start ngrok (unless you have a paid plan). To avoid OAuth failures, be sure to update your app settings in the Partner Dashboard each time the address changes (see next step for instructions).

Step 2: Create and configure your app in the Partner Dashboard

  1. Create a new app from your Partner Dashboard
  2. Set the App URL to:
 {https ngrok forwarding address}/shopify
  1. After creating the app, click on the App info tab, and set Whitelisted redirection URL(s) to:
 {https ngrok forwarding address}/shopify/callback
  1. Scroll down to the App credentials section, and take note of your API key and API secret key, you will supply them to your app as environment variables.

Tip

When published to the Shopify App Store, install requests will be sent to your App url, along with a shop parameter (e.g https://your-app.com/shopify?shop=your-development-shop.myshopify.com). You will make use of that parameter to generate an install URL that will redirect the merchant to the App authorization prompt (this process is explained in detail later in the tutorial).

Step 3: Create Node.js project

  1. Create a new folder named shopify-express-application, and use npm init from within the folder to create a package.json file.

    $ mkdir shopify-express-application
    $ cd shopify-express-application
    $ npm init
    

    When prompted, enter the application name as shopify-express-application, and hit RETURN to accept all the defaults suggested by NPM. Once completed, similar text will be printed to your console:

    {
      "name": "shopify-express-application",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "",
      "license": "ISC"
    }
    

    Your project now includes a package.json file, and it's structure should resemble:

    |- shopify-express-application
      |- package.json
    
  2. Use npm install to install required packages to your application

    $ npm install express dotenv cookie nonce request request-promise --save
    

    The --save command tells NPM to add the packages as dependencies in the package.json, which now reads:

    {
      "name": "shopify-express-application",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "",
      "license": "ISC",
      "dependencies": {
        "cookie": "^0.3.1",
        "dotenv": "^4.0.0",
        "express": "^4.15.4",
        "nonce": "^1.0.4",
        "request": "^2.82.0",
        "request-promise": "^4.2.1"
      }
    }
    
  3. Create a file called .env to store your Shopify API key and API secret key.

    SHOPIFY_API_KEY=YOUR_API_KEY
    SHOPIFY_API_SECRET=YOUR_API_SECRET_KEY
    

    The project should now look like:

    |- shopify-express-application
      |- node_modules
      |- package.json
      |- .env
    

    It is also recommended you create a .gitignore file in your project directory:

    |- shopify-express-application
      |- node_modules
      |- package.json
      |- .env
      |- .gitignore
    

    The contents of this file should include:

    .env
    

    Doing so ensures your credentials are never saved to GitHub (or any other Git provider you use).

Step 4: Start a Node.js app

  1. Create an index.js file

    |- shopify-express-application
      |- node_modules
      |- index.js
      |- package.json
      |- .env
    

    Add some boilerplate code to get your app running with a Hello World! route:

    const dotenv = require('dotenv').config();
    const express = require('express');
    const app = express();
    const crypto = require('crypto');
    const cookie = require('cookie');     
    const nonce = require('nonce')();
    const querystring = require('querystring');
    const request = require('request-promise');
    
    const apiKey = process.env.SHOPIFY_API_KEY;
    const apiSecret = process.env.SHOPIFY_API_SECRET;
    const scopes = 'read_products';
    const forwardingAddress = "{ngrok forwarding address}"; // Replace this with your HTTPS Forwarding address
    
    app.get('/', (req, res) => {
      res.send('Hello World!');
    });
    
    app.listen(3000, () => {
      console.log('Example app listening on port 3000!');
    });
    
    

    You can run your app with the node command.

    $ node index.js
    

    Visiting localhost:3000 from your browser will display Hello World!.

    Tip

    Since your ngrok tunnel is running, you can also visit your Forwarding address to open your app.

Step 5: Create Shopify specific routes

You require two Shopify specific routes in your application:

  1. Install route: This route expects a shop URL parameter and uses it to redirect the merchant to the Shopify App authorization prompt, where they can choose to accept or reject the installation request.

    app.get('/shopify', (req, res) => {
      const shop = req.query.shop;
      if (shop) {
        const state = nonce();
        const redirectUri = forwardingAddress + '/shopify/callback';
        const installUrl = 'https://' + shop +
          '/admin/oauth/authorize?client_id=' + apiKey +
          '&scope=' + scopes +
          '&state=' + state +
          '&redirect_uri=' + redirectUri;
    
        res.cookie('state', state);
        res.redirect(installUrl);
      } else {
        return res.status(400).send('Missing shop parameter. Please add ?shop=your-development-shop.myshopify.com to your request');
      }
    });
    

    The URL that redirects the merchant to the App authorization prompt must include the app's API key, scopes the app is requesting access to, a state parameter which will be used in the callback route to ensure the request originated from the app, and the redirect_uri the merchant will be sent to once they approve the request. In constructing this URL, you have made use of the forwardingAddress, apiKey, and scopes variables declared in the previous step.

    Restarting your app and visiting {your ngrok forwarding address}/shopify?shop=your-development-shop.myshopify.com should redirect you to the App authorization prompt for your development shop.

  2. Callback route: Once a user accepts the install request, Shopify sends them to the redirect_uri you specified in the previous step (this address must match the Whitelisted redirection URL(s) you set in the Partner Dashboard). The request from Shopify includes a code that needs to be exchanged for a permanent access_token.

    app.get('/shopify/callback', (req, res) => {
      const { shop, hmac, code, state } = req.query;
      const stateCookie = cookie.parse(req.headers.cookie).state;
    
      if (state !== stateCookie) {
        return res.status(403).send('Request origin cannot be verified');
      }
    
      if (shop && hmac && code) {
        res.status(200).send('Callback route');
    
        // TODO
        // Validate request is from Shopify
        // Exchange temporary code for a permanent access token
          // Use access token to make API call to 'shop' endpoint
      } else {
        res.status(400).send('Required parameters missing');
      }
    });
    

    The first step is to validate the request using HMAC validation. This step ensures the request has, in fact, come from Shopify. Add the following code to your route to authenticate the request:

    const map = Object.assign({}, req.query);
    delete map['signature'];
    delete map['hmac'];
    const message = querystring.stringify(map);
    const generatedHash = crypto
      .createHmac('sha256', apiSecret)
      .update(message)
      .digest('hex');
    
    if (generatedHash !== hmac) {
      return res.status(400).send('HMAC validation failed');
    }
    

    With the validation code included, your route will now look like this:

    app.get('/shopify/callback', (req, res) => {
      const { shop, hmac, code, state } = req.query;
      const stateCookie = cookie.parse(req.headers.cookie).state;
    
      if (state !== stateCookie) {
        return res.status(403).send('Request origin cannot be verified');
      }
    
      if (shop && hmac && code) {
        // DONE: Validate request is from Shopify
        const map = Object.assign({}, req.query);
        delete map['signature'];
        delete map['hmac'];
        const message = querystring.stringify(map);
        const generatedHash = crypto
          .createHmac('sha256', apiSecret)
          .update(message)
          .digest('hex');
    
        if (generatedHash !== hmac) {
          return res.status(400).send('HMAC validation failed');
        }
    
        res.status(200).send('HMAC validated');
        // TODO
        // Exchange temporary code for a permanent access token
          // Use access token to make API call to 'shop' endpoint
      } else {
        res.status(400).send('Required parameters missing');
      }
    });
    

    Run the app with node index.js, and visit {your ngrok address}/shopify?shop=your-development-shop.myshopify.com from your browser. If you previously accepted the install request, Shopify will not present you with the prompt again. Your browser should now load a page that reads HMAC validated.

    Next, add the following code to exchange the provided code for a permanent access_token:

    const accessTokenRequestUrl = 'https://' + shop +
      '/admin/oauth/access_token';
    const accessTokenPayload = {
      client_id: apiKey,
      client_secret: apiSecret,
      code,
    };
    
    request.post(accessTokenRequestUrl, { json: accessTokenPayload })
    .then((accessTokenResponse) => {
      const accessToken = accessTokenResponse.access_token;
    
      res.status(200).send("Got an access token, let's do something with it");
      // TODO
      // Use access token to make API call to 'shop' endpoint
    })
    .catch((error) => {
      res.status(error.statusCode).send(error.error.error_description);
    });
    

    Your route will now look like:

    app.get('/shopify/callback', (req, res) => {
      const { shop, hmac, code, state } = req.query;
      const stateCookie = cookie.parse(req.headers.cookie).state;
    
      if (state !== stateCookie) {
        return res.status(403).send('Request origin cannot be verified');
      }
    
      if (shop && hmac && code) {
        // DONE: Validate request is from Shopify
        const map = Object.assign({}, req.query);
        delete map['signature'];
        delete map['hmac'];
        const message = querystring.stringify(map);
        const generatedHash = crypto
          .createHmac('sha256', apiSecret)
          .update(message)
          .digest('hex');
    
        if (generatedHash !== hmac) {
          return res.status(400).send('HMAC validation failed');
        }
    
        // DONE: Exchange temporary code for a permanent access token
        const accessTokenRequestUrl = 'https://' + shop +
          '/admin/oauth/access_token';
        const accessTokenPayload = {
          client_id: apiKey,
          client_secret: apiSecret,
          code,
        };
    
        request.post(accessTokenRequestUrl, { json: accessTokenPayload })
        .then((accessTokenResponse) => {
          const accessToken = accessTokenResponse.access_token;
    
          res.status(200).send("Got an access token, let's do something with it");
          // TODO
          // Use access token to make API call to 'shop' endpoint
        })
        .catch((error) => {
          res.status(error.statusCode).send(error.error.error_description);
        });
    
      } else {
        res.status(400).send('Required parameters missing');
      }
    });
    

    Restarting the app and visiting {your ngrok address}/shopify?shop=your-development-shop.myshopify.com should now bring you to a page that reads Got an access token, let's do something with it.

    Finally, we can use the access token to make an API call to the shop endpoint:

    const shopRequestUrl = 'https://' + shop + '/admin/shop.json';
    const shopRequestHeaders = {
      'X-Shopify-Access-Token': accessToken,
    };
    
    request.get(shopRequestUrl, { headers: shopRequestHeaders })
    .then((shopResponse) => {
      res.end(shopResponse);
    })
    .catch((error) => {
      res.status(error.statusCode).send(error.error.error_description);
    });
    

    Your completed route should now look like:

    app.get('/shopify/callback', (req, res) => {
      const { shop, hmac, code, state } = req.query;
      const stateCookie = cookie.parse(req.headers.cookie).state;
    
      if (state !== stateCookie) {
        return res.status(403).send('Request origin cannot be verified');
      }
    
      if (shop && hmac && code) {
        // DONE: Validate request is from Shopify
        const map = Object.assign({}, req.query);
        delete map['signature'];
        delete map['hmac'];
        const message = querystring.stringify(map);
        const generatedHash = crypto
          .createHmac('sha256', apiSecret)
          .update(message)
          .digest('hex');
    
        if (generatedHash !== hmac) {
          return res.status(400).send('HMAC validation failed');
        }
    
        // DONE: Exchange temporary code for a permanent access token
        const accessTokenRequestUrl = 'https://' + shop +
          '/admin/oauth/access_token';
        const accessTokenPayload = {
          client_id: apiKey,
          client_secret: apiSecret,
          code,
        };
    
        request.post(accessTokenRequestUrl, { json: accessTokenPayload })
        .then((accessTokenResponse) => {
          const accessToken = accessTokenResponse.access_token;
          // DONE: Use access token to make API call to 'shop' endpoint
          const shopRequestUrl = 'https://' + shop + '/admin/shop.json';
          const shopRequestHeaders = {
            'X-Shopify-Access-Token': accessToken,
          };
    
          request.get(shopRequestUrl, { headers: shopRequestHeaders })
          .then((shopResponse) => {
            res.status(200).end(shopResponse);
          })
          .catch((error) => {
            res.status(error.statusCode).send(error.error.error_description);
          });
        })
        .catch((error) => {
          res.status(error.statusCode).send(error.error.error_description);
        });
    
      } else {
        res.status(400).send('Required parameters missing');
      }
    });
    

Step 6: Run your app

With your routes completed, start your app with the node index.js command, and visit {your ngrok forwarding address}/shopify?shop=your-development-shop.myshopify.com. This will bring you to a page that displays the raw JSON from your request to the shop endpoint.

Next steps

  1. Explore Node.js libraries such as Shopify Node API, and Shopify API Node
  2. Use the Embedded App SDK to allow merchants to load your app from within the Shopify admin