David Ekanem In love with technology and discussions around technology.

Build a Shopify app with Node.js

7 min read 2198 105

Build a Shopify app with Node.js

Shopify is a platform that enables businesses to create online stores and applications. An online store is critical to ecommerce businesses. The store needs to be efficient and built to serve the user. Shopify is one of the preferred platforms for the development of ecommerce applications.

Benefits of using Shopify range from a high-grade user experience to great frontend design. Shopify also provides great marketing strategies. In this article, our focus will be on building a Node.js application that works with Shopify.

Jump ahead:

Integrating with Shopify

Third-party developers develop a large portion of Shopify apps that serve to extend the base functionality of Shopify. With third-party applications, integrating new features into Shopify is super easy and they allow us to extend the merchant experience. With Shopify, we can create an amazing shopping experience for our customers.

There’s almost no limit to what we can do with third-party apps. Merchants can access and interact with our application within the Shopify admin dashboard.

Integrating with Shopify is possible in a few different ways:

  • Extending new features in the Shopify admin dashboard for the point-of-sale app
  • Integrating with Shopify APIs, e.g., the admin API to allow applications to read and write products, etc.

Shopify accepts two types of applications:

  1. Public applications: These applications are available for all businesses, which help in creating a partner dashboard
  2. Custom applications: Customized applications to meet specific business needs

Let’s set up our TypeScript and Node.js project.

Setting up our Node.js project with TypeScript

The requirements for setting up this type of application are as follows:

Create a folder to hold our project:

mkdir shopifyNode
cd shopifyNode

Using Yarn and the -y flag, we set up our package.json, which will approve all defaults during creation.

yarn init -y

Now, add TypeScript as a development dependency:

yarn add typescript

Once the installation of TypeScript completes, we gain access to the command line TypeScript compiler through the tsc command.

Installing Node.js types for TypeScript

TypeScript possesses different kinds of types. We will install the DefinitelyTyped type definition in this application.

yarn add --dev @types/node

Let’s create a tsconfig.json to hold the options for our TypeScript compiler. Let’s create tsconfig using the command line.

npx tsc --init --rootDir src --outDir build --esModuleInterop --resolveJsonModule --lib es6 --module commonjs --allowJs true --noImplicitAny true

Let’s break down the code above:

  • rootDir: This is the location in which TypeScript looks for the project code. In our case, it’s configured to the src folder
  • outDir: The code output from the compiler, which gets stored in the build/ folder
  • esModuleInterop: This handles the module system in our application. An example of module systems include AMD, SystemJS, ES Modules, etc.; for this application, we are setting CommonJS to true because it is the main module system for Node.js apps
  • resolveModule: Enables JSON in our TypeScript application
  • lib: Adds ambient types to our project and enables the application to rely on features from different ECMA Script versions. With this, we can use testing libraries, the DOM API, and ES 2015 langauge features — all of which gets compiled to ES 2009
  • module: CommonJS, standard module system for Node.js
  • allowjs: Converts our JavaScript project to TypeScript. With this, .js files are usable with .ts files
  • noImplicitAny: Every type needs to have a specific type or be explicitly defined as any

Once completed, the tsconfig.json file should look like the image below:
Our tsconfig.json file

Clean out the comments that aren’t needed:
Clean out the unnecessary comments in our tsconfig.json file

Finally, create the src folder and create an index TypeScript file.

mkdir src
touch src/index.ts

Compiling our TypeScript code

To compile, we rely on the tsc command using npx. tsc reads the tsconfig.json in the current directory and applies the configuration. The TypeScript compiler generates the compiled JavaScript code.



npx tsx

Once this command runs in the build folder, it creates an index.js file. Our first TypeScript file!
The TypeScript file has been created in our index.js file

Adding dependencies and scripts

Our application will have cold reloading enabled, which in turn enables faster development times when developing locally. Implementing cold reloading in our application relies on a couple of packages:

  1. Ts-node: Runs TypeScript code without waiting for compilation
  2. nodemon: Watches changes in our code and restarts when a file changes
    yarn add ts-node nodemon

Add a nodemon.json config:

touch nodemon.json

Paste the code block into the nodemon.json file:

{
  "watch": ["src"],
  "ext": ".ts,.js",
  "ignore": [],
  "exec": "ts-node ./src/index.ts"
}

In our package.json, add a script to manage nodemon:

"scripts": {
    "start:dev": "nodemon",
  },

When yarn run start:dev runs, nodemon starts our application using ts-node in the ./src/index.ts. It looks for changes to .ts and .js files with /src.

Initializing our application

In the index.ts file, let’s start building our application.

code index.ts

Let’s import the necessary dependencies and initialize our application.

const crypto = require("crypto");
const nonce = require("nonce");
const request = require("request-promise");
const querystring = require("querystring");
const cookie = require("cookie");
const express = require("express");

const app = express(); // initialize application
app.listen(3434, () => console.log("Application listening on port 3434!"));

Hosting the server

ngrok enables us to host our local server and make it publicly available. Once ngrok is downloaded, install and open it. A terminal window should fire up; input the following command to host the server with ngrok.

ngrok http 3434

You should then see this:
Hosting our server with ngrok

Creating a new application in Shopify’s partner dashboard

Navigate to the Shopify Partners dashboard.
The Get Started page in the Shopify Partners dashboard

Click the Apps tab:
The Apps tab in the Shopify Partner dashboard

Click the Create app button:
Click create app

You’ll be prompted to decide how to create the application. You can use either the Shopify CLI or create the app manually. Input your desired name; for this article, our name is dueWidget. Click the Create button.
Create a new app in the new app page

Once we click the Create button, the CLIENT ID and CLIENT SECRET are displayed. We’ll need these details to authenticate our application.
Input the client credentials for your store
In the navigation tab, click App setup.
The App Setup page

In the section under URLs, copy the generated ngrok URL and paste it into App URL and Redirection URL. The URL in my console is https://d9f5-102-219-153-216.eu.ngrok.io/.

Append /shopify to the ngrok URL to give us access to the user-route. The App URL is the URL we redirect merchants to once they click our app listing.

The Redirection URL works to redirect the merchant once they enable the application. We add a /callback to identify it.
The Redirection URL callback

Save, return to the Overview section, then go to the Test your app section and click the Select store button. Here, we’ll connect our application to the development store.

The link to creating the development store is in the Requirements section above.
Test app on development store

Developing our Node.js application

With that all set up, let’s run back to our IDE and continue developing our application inside the index.ts file.

Using a terminal window, open the index.ts file, and create an environment variable and gitignore file in the root project directory. The env file enables us to securely store our secrets, and the gitignore prevents us from deploying unwanted files to Git.

code index.ts
touch .env .gitignore
code .gitignore

Add the line below into the .gitignore file:

.env

Using a terminal window, open the .env file:

code .env

The details from the image below are what’s needed. The Client ID represents your SHOPIFY_API_KEY. The Client Secret represents your SHOPIFY_API_SECRET.

SHOPIFY_API_KEY= //YOUR CLIENT ID FROM SHOPIFY APP OVER PAGE
SHOPIFY_API_SECRET=//YOUR CLIENT SECRET FROM SHOPIFY APP OVER PAGE

The details can be found in the Application Overview page of our Shopify application dashboard.
Input the client credentials for your store

In the index.ts file, let’s continue writing our code.

We are going to define two endpoints in our application:

  1. The /shopify route: This receives the name of the shop in the request and creates a nonce to manage state. The nonce authenticates that the request to the callback URL is from Shopify
  2. The /shopify/callback route: Handles the callback from Shopify. Once we are certain that the request emanated from Shopify, the user is redirected

Copy the code block below and paste it into your index.ts file:

const PORT = 3342;
const apiKey = process.env.SHOPIFY_API_KEY;
const apiSecret = process.env.SHOPIFY_API_SECRET;
const scopes = "write_products";
const forwardingAddress = "https://1649-102-88-34-8.eu.ngrok.io"; // our ngrok url

// set the url, to the url used in the APP URL
app.get("/shopify", (req: any, res: any) => {
  const shopName = req.query.shop;
  if (shopName) {
    // use nonce to set a parameter called state
    // the nonce is random string that would be set
    // it would be received on the request
    // the callback from shopify would echo the state
    // the two states would be compared
    // if they match, we are sure the request came from shopify
    // if they don't match, they request is being spoofed
    // this would throw an error
    const shopState = nonce();
    // shopify callback redirect
    const redirectURL = forwardingAddress + "/shopify/callback";

    // install url for app install
    const installUrl =
      "https://" +
      shopName +
      "/admin/oauth/authorize?client_id=" +
      apiKey +
      "&scope=" +
      scopes +
      "&state=" +
      shopState +
      "&redirect_uri=" +
      redirectURL;

    // in a production app, the cookie should be encrypted
    // but, for the purpose of this application, we won't do that
    res.cookie("state", shopState);
    // redirect the user to the installUrl
    res.redirect(installUrl);
  } else {
    return res.status(400).send('Missing "Shop Name" parameter!!');
  }
});

The callback URL redirects the user. This route checks whether or not the state stored in the cookie is from Shopify. If it is, we generate a hash and use it for HMAC validation. If this validation fails, the URL install fails.

If HMAC validation is true, the application installation in the development store is successful.

app.get("/shopify/callback", (req: any, res: any) => {
  const { shop, hmac, code, shopState } = req.query;
  const stateCookie = cookie.parse(req.headers.cookie).shopState;

  if (shopState !== stateCookie) {
    return res.status(400).send("request origin cannot be found");
  }

  if (shop && hmac && code) {
    const Map = Object.assign({}, req.query);
    delete Map["hmac"];
    delete Map["signature"];

    const message = querystring.stringify(Map);
    const providedHmac = Buffer.from(hmac, "utf-8");
    const generatedHash = Buffer.from(
      crypto
        .createHmac("sha256", process.env.SHOPIFY_API_SECRET)
        .update(message)
        .digest("hex"),
      "utf-8"
    );
    let hashEquals = false;
    try {
      hashEquals = crypto.timingSafeEqual(generatedHash, providedHmac);
    } catch (e) {
      hashEquals = false;
    }
    if (!hashEquals) {
      return res.status(400).send("HMAC validation failed");
    }
    const accessTokenRequestUrl =
      "https://" + shop + "/admin/oauth/access_token";
    const accessTokenPayload = {
      client_id: process.env.SHOPIFY_API_KEY,
      client_secret: process.env.SHOPIFY_API_SECRET,
      code,
    };
    request
      .post(accessTokenRequestUrl, { json: accessTokenPayload })

      .then((accessTokenResponse: any) => {
        const accessToken = accessTokenResponse.access_token;

        const apiRequestURL = `https:// + ${shop} + /admin/shop.json`;

        const apiRequestHeaders = {
          "X-Shopify-Access-Token": accessToken,
        };

        request
          .get(apiRequestURL, { headers: apiRequestHeaders })

          .then((apiResponse: any) => {
            res.end(apiResponse);
          })

          .catch((error: any) => {
            res.status(error.statusCode).send(error.error.error_description);
          });
      })

      .catch((error: any) => {
        res.status(error.statusCode).send(error.error.error_description);
      });
  } else {
    return res.status(400).send("required parameter missing");
  }
});

app.listen(PORT, () => console.log(`Application listening on port ${PORT}`));

Testing our application

It’s time to test our application. The URL to be used is:

https://5235-102-88-62-59.eu.ngrok.io/shopify?shop=duetestapplicationstore.myshopify.com/

This URL can be sent to prospective merchants to install our application in their Shopify store.
Install the app

You’ll be redirected to the installation screen. Click the Install App button. If the installation is successful, the app will be displayed in the Application Overview page.
Installation is successful

The application installation is successful. Our application is successfully integrated with the duetestapplication store that we created. If we return to our development store, we should see our application under the Apps section.
Our app appears in the partner dashboard
Click dueWidget to see the functionality of our application.
See the functionality of our app

Our application works as intended, enabling us to read information about our store.

There are various other ways that we can extend our application. In developing our application, we added the ability to write_products under scopes, which allows us to add products to our Shopify store.

Conclusion

In this article, we learned how to set up a development store on Shopify and make use of the Shopify API to retrieve data. We also learned how to create a Node.js application and host our server with ngrok.

Then, we connected our application to our development store and used it to retrieve details about the store. The steps outlined in this article enable developers to build applications for Shopify using Node and enables merchants to extend the functionality of their stores.

There are many more functionalities that can be implemented, ranging from adding products to our store, etc. Try out some of them to see what your Shopify store can do for you!

LogRocket: See the technical and UX reasons for why users don’t complete a step in your ecommerce flow.

LogRocket is like a DVR for web and mobile apps and websites, recording literally everything that happens on your ecommerce app. Instead of guessing why users don’t convert, LogRocket proactively surfaces the root cause of issues that are preventing conversion in your funnel, such as JavaScript errors or dead clicks. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Start proactively monitoring your ecommerce apps — .

David Ekanem In love with technology and discussions around technology.

One Reply to “Build a Shopify app with Node.js”

  1. Dear David,

    I thank you for incredibly helpful blog post on implementing a Shopify application. Your step-by-step guide was incredibly clear and easy to follow, and it was exactly what I needed to get started on my own project.

    I have made significant progress since reading your post, but I have recently run into some roadblocks during the testing stage. I was wondering if I could ask for your help in resolving these issues. Specifically, I am struggling with testing our app stage where
    1. i am passing the shop name must be ours or the merchants ? .
    2. i am not getting any install app screen after hitting the URL
    3. when i am passing another shop names giving me message like “It looks like you don’t have access to this store. Contact the store administrator for access.”
    4. I also want to use data of that shop to integrate with my app inshort i want the database access so i can move that data forward to my application for further processing.
    If you have any insights or solutions that you could offer, it would be greatly appreciated.

    If it’s convenient for you, please feel free to contact me by email at [email protected] or through any other preferred means of communication. Thank you again for your fantastic blog post, and I hope to hear from you soon.

Leave a Reply