JavaScriptNext.jsReactSaaS / FaaSShopify

Create Shopify App using Next.js & Koa with TypeScript

Basically, we will create a new Shopify application by using shopify node create. But if using the command, we […]

広告ここから
広告ここまで

Basically, we will create a new Shopify application by using shopify node create. But if using the command, we need to update the template to support TypeScript manually.

So, I’ve tried another way to create a new Shopify application.

Use create-next-app first

The other way is using create-next-app. It can create a Next.js application supporting TypeScript by default.

$ yarn create next-app --typescript

Install Koa and Shopify libraries

Next, we need to install Koa and Shopify libraries.

$ yarn add @shopify/shopify-api @shopify/koa-shopify-auth koa koa-router
$ yarn add -D @types/koa @types/koa-router nodemon ts-node

Create Koa server code

We need to pass authentication by koa-shopify-auth, so we need to start the server by using Koa.

Create these new files to prepare the new server using Koa.

tsconfig.server.json

{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "module": "commonjs",
        "outDir": "dist",
        "target": "es2017",
        "isolatedModules": false,
        "noEmit": false
    },
    "include": [
        "server/*.ts",
        "server/**/*.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}

nodemon.json

{
    "watch": ["server"],
    "exec": "ts-node --project tsconfig.server.json server/index.ts",
    "ext": "js ts"
  }

server/index.ts

This is a new server code. It’s almost the same as Shopify’s template code.

import Koa from 'koa'
import Router from 'koa-router'
import next from 'next'
import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "@shopify/shopify-api";
import dotenv from 'dotenv'

dotenv.config()

const {
  PORT,
  NODE_ENV,
  SHOPIFY_API_KEY,
  SHOPIFY_API_SECRET,
  SCOPES,
  HOST
} = process.env as any

const port = parseInt(PORT, 10) || 8081;
const dev = NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

Shopify.Context.initialize({
    API_KEY: SHOPIFY_API_KEY,
    API_SECRET_KEY: SHOPIFY_API_SECRET,
    SCOPES: SCOPES.split(","),
    HOST_NAME: HOST.replace(/https:\/\//, ""),
    API_VERSION: ApiVersion.October20,
    IS_EMBEDDED_APP: true,
    // This should be replaced with your preferred storage strategy
    SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
  });
  
// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS: {
    [shop: string]: string
} = {};


const handleRequest = async (ctx: any) => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
  };

app.prepare().then(() => {

    const server = new Koa()
    const router = new Router()
    server.keys = [Shopify.Context.API_SECRET_KEY];
    server.use(
      createShopifyAuth({
        async afterAuth(ctx) {
          // Access token and shop available in ctx.state.shopify
          const { shop, accessToken, scope } = ctx.state.shopify;
          const host = ctx.query.host;
          ACTIVE_SHOPIFY_SHOPS[shop] = scope;
  
          const response = await Shopify.Webhooks.Registry.register({
            shop,
            accessToken,
            path: "/webhooks",
            topic: "APP_UNINSTALLED",
            webhookHandler: async (topic, shop, body) => {
              if (shop && ACTIVE_SHOPIFY_SHOPS[shop]) { 
                delete ACTIVE_SHOPIFY_SHOPS[shop]
              }
            },
          });
  
          if (!response.success) {
            console.log(
              `Failed to register APP_UNINSTALLED webhook: ${response.result}`
            );
          }
  
          // Redirect to app with shop parameter upon auth
          ctx.redirect(`/?shop=${shop}&host=${host}`);
        },
      })
    );

  router.post("/webhooks", async (ctx) => {
    try {
      await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
      console.log(`Webhook processed, returned status code 200`);
    } catch (error) {
      console.log(`Failed to process webhook: ${error}`);
    }
  });

  router.post(
    "/graphql",
    verifyRequest({ returnHeader: true }),
    async (ctx) => {
      await Shopify.Utils.graphqlProxy(ctx.req, ctx.res);
    }
  );

  router.get("(/_next/static/.*)", handleRequest); // Static content is clear
  router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
  router.get("(.*)", async (ctx) => {
    const shop = (() => {
        const { shop } = ctx.query
        if (!shop) return undefined
        if (typeof shop === 'string') return shop
        return shop[0]
    })();

    // This shop hasn't been seen yet, go through OAuth to create a session
    if (!shop || ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
      ctx.redirect(`/auth?shop=${shop}`);
    } else {
      await handleRequest(ctx);
    }
  });

  server.use(router.allowedMethods());
  server.use(router.routes());
  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });

})

.shopify-cli.yml

When placing the file manually, we can use stripe node connect to connect your Shopify application.

---
 project_type: node
 organization_id: YOUR_SHOPIFY_PARTNER_ID

Connect to existing Shopify application

Then, using shopify node connect command to connect to your existing application on your Shopify Partner dashboard. And we’ll get a new .env file to connect from the application to Shopify.

shopify node connect

Update package.json to run the new server

We need to update the yarn dev command to start the new server.

  "scripts": {
-    "dev": "next dev",
+    "dev": "NODE_ENV=development nodemon",

Start the dev server by using Shopify APP CLI

Finally, we can run the server by using this command.

$ shopify node serve

If it works, you can see this page.

Conclusion

This example is another way to create a new Shopify Application with Next.js and TypeScript.
We need to set up the Shopify connection manually, but we don’t have to replace or update the Next.js code.
On the other hand, the official way that using shopify node create can make a new app simple and ready to connect to Shopify.

I hope you can select these ways to start a new Shopify application in your preferred way.

ブックマークや限定記事(予定)など

WP Kyotoサポーター募集中

WordPressやフロントエンドアプリのホスティング、Algolia・AWSなどのサービス利用料を支援する「WP Kyotoサポーター」を募集しています。
月額または年額の有料プランを契約すると、ブックマーク機能などのサポーター限定機能がご利用いただけます。

14日間のトライアルも用意しておりますので、「このサイトよく見るな」という方はぜひご検討ください。

広告ここから
広告ここまで

Related Category posts