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.

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

    Random posts

    Home
    Search
    Bookmark