skip to Main Content

I am new to Shopify App Devlopment, especially the Shopify API.

I create a working app with the Shopify CLI and now want to communicate with the API.

I try to access following endpoint: https://{my_shop]/admin/api/2021-07/shop.json

I learned that I need some access token and the shop name to access this endpoint.

I created an access token under my private apps section.

But I dont know how to get the currently logged in store.

For example, when clicking a button in my frontend, I would like to call my endpoint, which in turn calls the Shopify API endpoint and retrieves the information. How do I do this the right way? And how do I get the currently logged in shop?

This is my code so far:

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

dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8081;
const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
});
const handle = app.getRequestHandler();

Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
  SCOPES: process.env.SCOPES.split(","),
  HOST_NAME: process.env.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 = {};

app.prepare().then(async () => {
  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) =>
            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.get("/test2", verifyRequest(), async(ctx, res) => {
    const {shop, accessToken } = ctx.session;
    console.log(shop);
    console.log(accessToken);
  })

  router.get("/test", async (ctx) => {

    const config = {
      headers: {
        'Content-Type': 'application/json',
        'X-Shopify-Access-Token': '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'
      }
    }

    await axios.get('${the_store_name_belongs_here}/admin/api/2021-07/shop.json', config).then(res => {
      ctx.body = res.data;
    });
  });

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

  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, next) => {
      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 = ctx.query.shop;

    // This shop hasn't been seen yet, go through OAuth to create a session
    if (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}`);
  });
});

Please have a look at my attempts – endpoint /test and endpoint /test2.
test2 is not working. ctx.session is null. ctx itself is null. Why?

test1 is working when I hard code my shops name into the url, then I get the desired data. But how do I put a shop variable inside? That’s my struggle.

3

Answers


  1. There is no reference to any ctx.session in koa-shopify-auth documentation. What about this:

    router.get("/test2", verifyRequest(), async(ctx) => {
      const { shop, accessToken } = ctx.state.shopify;
      console.log(shop, accessToken);
    })
    

    Other solutions

    You can store a Cookie after authentication

    afterAuth(ctx) {
        const { shop, accessToken } = ctx.session;
        ctx.cookies.set("shop", shop, { httpOnly: false, secure: true, sameSite: "none" });
        ctx.redirect("/");
    },
    

    And then read it in future requests:

    router.get("/test2", verifyRequest(), async(ctx) => {
      const shop = ctx.cookies.get("shop");
      console.log(shop);
    })
    
    Login or Signup to reply.
  2. I faced this problem and resolved it by passing the shop as a query parameter.

    I call the endpoint with:

    axios.get('/test', {
      params: {
        shop: 'fo.myshopify.com'
      }
    });
    

    And get the shop with:

    router.get("/test", async (ctx) => {
      const shop = ctx.query.shop;
      ...
    });
    

    Of course, you have to know the shop where you call the endpoint.

    Login or Signup to reply.
  3. First of all, it is not good practice to use MemorySessionStorage in production environment due to its limitations, you can find a good explanation here

    MemorySessionStorage exists as an option to help you get started
    developing your apps as quickly as possible…

    So, implement a CustomSessionStorage (refer to the doc above), you will have access to the session that stores data such as shop, accessToken, scope among others. As long as authenticated requests are made, providing the JWT in the header, you can get the context properly working.

    e.g. (react-koa):

    //client.js
    import { useAppBridge } from "@shopify/app-bridge-react";
    import { getSessionToken } from "@shopify/app-bridge-utils";
    
    function Index() {
       const app = useAppBridge();
    
       async function getProducts() {
           const token = await getSessionToken(app);
    
           const response = await fetch("/api/products", {
               headers: { "Authorization": `Bearer ${token}` }
           });
    
           const result = await response.json();
           console.log(result);
       }
    
       return (<></>);
    }
    

    and then…

    // server.js
    
    router.get("/api/products", verifyRequest({ returnHeader: true }), async (ctx) => {
        // Load the current session to get the `accessToken`.
        // pass a third parameter clarifying the accessMode (isOnline = true by default)
        const session = await Shopify.Utils.loadCurrentSession(ctx.req, ctx.res);
    
        // Create a new client for the specified shop.
        const client = new Shopify.Clients.Rest(session.shop, session.accessToken);
    
        // Use `client.get` to request the specified Shopify REST API endpoint, in this case `products`.
        const products = await client.get({
          path: 'products',
        });
    
        ctx.body = results.body;
        ctx.res.status = 200;
      });
    

    more details here.

    Using axios, you can define it as a hook (working example using TypeScript):

    import axios from 'axios';
    import { useAppBridge } from '@shopify/app-bridge-react';
    import { getSessionToken } from '@shopify/app-bridge-utils';
    
    function useAxios() {
      const app = useAppBridge();
      const instance = axios.create();
      instance.interceptors.request.use(async function (config) {
        const token = await getSessionToken(app);
        config.headers['Authorization'] = `Bearer ${token}`;
        return config;
      });
      return [instance];
    }
    
    export default useAxios;
    
    // index.js
    
    // ...
    const [axios] = useAxios();
    
    // ...
    const result = await axios.get('/api/products');
    console.log(result.data);
    // ...
    

    Hope this helps anyone who is still looking for help.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search