snow-mountain

Kick-Off Your First Shopify App With React, NodeJS and Common Ninja


Last updated on
Common NinjaDeveloper PlatformWeb Development

Summary (TL;DR): In this article, we discuss the process of building a Shopify app using React, Node.js and Common Ninja.

Let’s start at the end. There are tons of tutorials and resources out there on how to build apps for Shopify, so what’s so special about this one?

Well, I’m glad you asked! 😉

If you had any previous experience with building Shopify apps, you probably wasted endless time on reading their documentation, trying to understand the authentication flow, connecting to webhooks, testing different ways to charge money for your app — basically – trying to connect everything together.

The thing is, once you’ve finished building your app (and presuming that you’ve survived the above process) it will only be available to Shopify merchants. But what about other e-commerce platforms like WooCommerce, Wix, BigCommerce, etc.? Your app is probably suitable for them as well. But in order to make it work on those platforms, you’ll need to rewrite your app from scratch and, in some cases, you’ll need to add more capabilities that aren’t supported by these platforms, like monetization and payments.

You can read more about The Problems With Building Apps for E-Commerce Platforms in a previous post we wrote.

At Common Ninja we solve these problems by providing developers with a single interface for E-Commerce and payments platforms, allowing you to focus solely on building the business logic of your app, rather than spending too much time on integrating it with multiple platforms.

In this article, I’ll walk you through the process of building your first Shopify app with Common Ninja, NodeJS, and React. And the best part? Once your app is ready, you can use the same code to monetize it on other e-commerce platforms that we support! 🚀

Requirements

Before we start, there are a few things you need to set, so make sure you have the following:

  • A developer account on Common Ninja.
  • A partner account on Shopify.
  • An integration between Shopify and Common Ninja. See a tutorial here.

Technical requirements:

Installation

If you have all of the above installed you can start running the following code to create a comfortable environment and start working on your app with a simple integration to Common Ninja:

npx @commonninja/create-nindo-app project-name

You’ll be asked to select a template. For the purpose of this quick start tutorial, choose the Server template.

Nindo CLI

The project will automatically install all of its dependencies.

Setup

Open the generate project in VSCode – or any other code editor you’re using, and edit its .env file:

# Env
PORT=4000

# Common Ninja App
COMMONNINJA_APP_ID=
COMMONNINJA_APP_SECRET= 

Set the COMMONNINJA_APP_ID and the COMMONNINJA_APP_SECRET based on your Common Ninja app (if you haven’t created an app yet, now is the time):

App Dashboard

Next, start the development server by running:

docker compose up

Make sure that the server is up and navigate to localhost:4000. You should see Hey there message.

Project Structure

This is a very simple and basic NodeJS project with Docker. It is already set up with TypeScript and an automatic watch on files for server restart. Under the src folder there are two files:

src
    app.ts
    routes.ts

app.ts

A very common NodeJS app entry file. Here we set up the initial Express server:

import 'dotenv/config';

import express from 'express';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';

import router from './routes';

const port = parseInt(process.env.PORT || '3000');
const app = express();

// Parse cookies
app.use(cookieParser());

// Parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));

// Parse application/json
app.use(
    bodyParser.json({
        limit: '50mb',
    })
);

app.use('/', router);

// Start server
app.listen(port, () => {
    console.log(`Running at http://localhost:${port}`);
});

module.exports = app;

Nothing special here.

routes.ts

This is an example of how to use Common Ninja’s Node SDK library. Each one of the routes is initializing the CommonNinja class:

const { COMMONNINJA_APP_ID, COMMONNINJA_APP_SECRET } = process.env;

function getCommonNinjaClient(req: Request) {
    if (!COMMONNINJA_APP_ID || !COMMONNINJA_APP_SECRET) {
        throw new Error('Missing Common Ninja app ID or secret key.');
    }

    // Create a new Common Ninja instance
    return new CommonNinja({
        appId: COMMONNINJA_APP_ID,
        appSecret: COMMONNINJA_APP_SECRET,
        accessToken: req.query.token as string,
        env: CommonNinja.envs.production,
        logs: true,
    });
}

In theroutes.ts file we define 3 routes, each one interacts with different parts of Common Ninja’s API.

/connect

The /connect route is responsible for redirecting your user to an authentication flow with the platforms your app supports. So if you’ve added the Shopify and WooCommerce integrations to your Common Ninja app – by browsing to http://localhost:4000 you’ll see these platforms’ logos.

Common Ninja connect

By clicking on one of them you’ll be redirected to the platform’s authentication page. Once the authentication flow is done, Common Ninja will redirect the user to the Redirect URL you defined in the relevant section of your app’s dashboard.

Redirect URL setup

The code that handles this route is using the SDK’s getConnectUrl method:

// Authentication
router.get('/connect', async (req: Request, res: Response) => {
    // Get a new Common Ninja instance
    const client = getCommonNinjaClient(req);

    // Get authentication url for platform
    const url = client.auth.getConnectUrl();

    // Redirect to authentication url
    res.redirect(url);
});

/api

This route is in fact a proxy to Common Ninja’s API. The SDK has a built-in proxy method that handles the incoming requests:

// API Proxy
router.all('/api*', async (req: Request, res: Response, next: NextFunction) => {
  // Get a new Common Ninja instance
  const client = getCommonNinjaClient(req);

  // Proxy api requests to Common Ninja API
  return client.apiProxyMiddleware(req, res, next, '/api');
});

The 4th parameter in the apiProxyMiddleware method is the route in your app that will be rewritten.

For example:
/api/ecommerce/products will be proxied tohttps://api.commonninja.com/integrations/api/v1/ecommerce/products.

Please note: You don’t have to use the API that way, instead you can support selected APIs by using the different methods the SDK supports. For example:

router.get('/api/products', async (req, res, next) => {
    const client = getCommonNinjaClient(req);
    const data = await client.ecommerce.getProducts();
    res.send(data);
});

/webhooks

Lastly, the /webhooks route will accept webhook messages from Common Ninja’s supported platforms. You may read more about Webhooks here.

Webhook messages should be validated, and our SDK will take care of that for you:

// Validate and handle Common Ninja's webhooks
router.post('/webhooks', async (req: Request, res: Response) => {
    try {
        const client = getCommonNinjaClient(req);

        // Validate webhook message source
        const validated = client.webhooks.validateWebhook(req);
        if (!validated) {
            throw new Error('Cannot validate signature.');
        }

        console.log('Webhook message', req.body);

        // Send a 200 OK response back to Common Ninja
        res.sendStatus(200);
    } catch (e) {
        console.error(`Cannot handle webhook message`, e);
        res.status(500).send((e as Error).message);
    }
});

In order to test the webhooks, you’ll need to work with https protocol locally. The way we handle it is by using tunnels.
This project has another docker compose file for this purpose. Run:

docker compose -f docker-compose.https.yml up

And you’ll get a secured URL that you can use to test the webhooks integration:

HTTPS with Localtunnel

Once you have the URL, simply add a new webhook URL to your Common Ninja app:

Setting up webhook URLs

Client

Now that we have the server ready, let’s create a simple react app using React CRA:

npx create-react-app client-app

Open the generated project, look for the package.json file, and add a proxy property:

{
  ...
  "proxy": "http://localhost:4000",
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  ...
}

React will proxy requests to the server we created.

Next, let’s start the project withnpm start.

Just for the sake of the simplicity, we’ll see how easy it is to access Common Ninja APIs through the app, without adding a single line of code to the server.

Let’s edit the App.jsxfile.

import { useState, useEffect } from 'react';

import './App.css';

function App() {
  const searchParams = new URLSearchParams(window.location.search);
  const [token] = useState(searchParams.get('token'));
  const [products, setProducts] = useState([]);

  async function getProducts() {
    try {
      const data = await fetch(`/api/ecommerce/products?token=${token}`);
      const response = await data.json();
      setProducts(response.data?.items);
    } catch (e) {
      console.log(e);
    }
  }

  function renderProducts() {
    return (
      <>
        <h1>Products</h1>
        <div className="products">
          {products.map((product) => (
            <div>
              <img src={product.images[0]} alt={product.name} />
              <p>{product.title}</p>
            </div>
          ))}
        </div>
      </>
    );
  }

  useEffect(() => {
    if (token) {
      getProducts();
    }
    // eslint-disable-next-line
  }, []);

  return (
    <div className="App">
      {token ? (
        renderProducts()
      ) : (
        <a href="http://localhost:4000/connect">Click to Connect</a>
      )}
    </div>
  );
}

export default App;

54 lines of code (even less) is all it takes to get a products list from Shopify API (or any other e-commerce platform), using Common Ninja.

In the first 3 lines of the function we set an initial state for the token and products list. In case the user has already gone through the authentication process, the url of the page will include a unique token, generated by Common Ninja that allows him to access his store’s resources.

In the useEffect hook, we make sure there’s a token in the url. If there is, we’re making an API call to our server.

useEffect(() => {
    if (token) {
      getProducts();
    }
}, []);

The getProducts function is using the fetch API to make a simple request to our server with the generated token:

async function getProducts() {
    try {
      const data = await fetch(`/api/ecommerce/products?token=${token}`);
      const response = await data.json();
      setProducts(response.data?.items);
    } catch (e) {
      console.log(e);
    }
}

The render function is also pretty straight forward. If there’s a token in the URL, we show a list of products, else, we render a connect button that leads to the /connect route we mentioned before.

return (
    <div className="App">
      {token ? (
        renderProducts()
      ) : (
        <a href="http://localhost:4000/connect">Click to Connect</a>
      )}
    </div>
);

The renderProduct function simply iterates on the products list that the API returns, as described in the docs.

function renderProducts() {
    return (
      <>
        <h1>Products</h1>
        <div className="products">
          {products.map((product) => (
            <div>
              <img src={product.images[0]} alt={product.name} />
              <p>{product.title}</p>
            </div>
          ))}
        </div>
      </>
    );
}

And vualah! Here’s the result:

Products API

Summary

In this article, we’ve learned how to create a Shopify app using React, NodeJS, and Common Ninja.

We saw how easy and simple it is to connect your app to Shopify, without being worried about authentication, APIs, and other complexities.

This is an example of a very simple app, but Common Ninja supports much complex use cases.

You can learn more about our e-commerce and payments APIs in our docs, and as always, we’ll be more than happy to help you to get your app up and running so if you have any questions feel free to reach out!

Meanwhile, join our growing Discord community! 🔥