Blog

Shopify Admin GraphQL API + gql.tada = ❤️

Say goodbye to codegen

Codegen can be annoying! You have to manage a separate process in order to leverage type safety. In this guide we’ll walk through how to use gql.tada for making type-safe calls to the Shopify Admin API in your Remix app.

Installation

Follow the installation instructions to get gql.tada set up properly in your project.

For the schema in your tsconfig plugin configuration, you can specify a direct proxy URL from Shopify that allows for introspection:

{
  "compilerOptions": {
    "plugins": [
      {
        "name": "@0no-co/graphqlsp",
        "schema": "https://shopify.dev/admin-graphql-direct-proxy/2024-07",
        "tadaOutputLocation": "./app/graphql-env.d.ts"
      }
    ]
  }
}

Make sure to replace 2024-07 with your API version if applicable.

GraphQL Client Setup

Install urql, a GraphQL client which will facilitate our queries and mutations to the Shopify Admin API.

Create a function that will create a client for a given shop:

// ~/app/graphql.server.ts

import { cacheExchange, Client, fetchExchange } from '@urql/core';
import { prisma } from '~/db.server';

const API_VERSION = '2024-07';

export const createShopifyClient = async (shop: string) => {
  const session = await prisma.session.findFirst({
    where: { shop, isOnline: false }
  });

  if (!session) throw new Error(`Session not found for shop: ${shop}`);

  return new Client({
    url: `https://${shop}/admin/api/${API_VERSION}/graphql.json`,
    exchanges: [cacheExchange, fetchExchange],
    fetchOptions: {
      headers: {
        'X-Shopify-Access-Token': session.accessToken
      }
    }
  });
};

Integrate with authenticate.admin

Shopify’s Remix app template includes a shopify.server.ts file which provides an export called authenticate. This is what facilitates authentication with Shopify and gives you a way to make Admin API queries. Augment it to include the urql client:

// ... shopifyApp setup ...

export const authenticate = {
  /**
   * Authenticates with Shopify, allowing access to Shopify's APIs when accessing
   * the app admin.
   *
   * Note: This injects a `urql` client, which can consume types from `gql.tada`. You should use this over
   * `admin.graphql` whenever possible for type safe variables and response data. Example:
   *
   * ```ts
   * const { admin, session } = await authenticate.admin(request);
   *
   * const response = await admin.urql.mutation(yourGraphQLQuery, {
   *   someVariable: 'hello world!'
   * });
   * 
   */
  admin: async (request: Request) => {
    const { session, admin, ...rest } = await shopify.authenticate.admin(request);
    const client = await createShopifyClient(session.shop);

    return { session, admin: { urql: client, ...admin }, ...rest };
  }
}; 

// ...

Example usage

Here’s an example route that queries a list of products from the Shopify API, using a query definition created with gql.tada:

import { json, LoaderFunctionArgs } from '@remix-run/node';
import { BlockStack, List, TextField } from '@shopify/polaris';
import { Form, useLoaderData } from '@remix-run/react';
import { useState } from 'react';
import { authenticate } from '~/shopify.server';

// Make sure the import path here is correct, aligned with how
// you set up gql.tada!
import { graphql } from '~/shared/graphql';

const productsQuery = graphql(`
  query Products($search: String!) {
    products(query: $search, first: 50) {
      edges {
        node {
          id
          title
        }
      }
    }
  }
`);

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { admin } = await authenticate.admin(request);
  const searchParams = new URL(request.url).searchParams;

  const response = await admin.urql.query(productsQuery, {
    search: searchParams.get('search') || ''
  });

  return json({
    products: response.data?.products.edges.map(edge => edge.node) || []
  });
};

export default function Products() {
  const { products } = useLoaderData<typeof loader>();
  const [search, setSearch] = useState('');

  return (
    <BlockStack>
      <Form>
        <TextField
          label="Search"
          autoComplete="off"
          name="search"
          value={search}
          onChange={setSearch}
        />
      </Form>

      <List>
        {products.map(product => (
          <List.Item key={product.id}>{product.title}</List.Item>
        ))}
      </List>
    </BlockStack>
  );
}

Notice how the query variables and response data are all typed properly, without the need for you to manage a separate process for generating types for your query documents.

Find this helpful? You might also like our apps:

  • Customer Fields allows you to build custom registration forms and retrieve valuable data from customers at just the right time.
  • Meteor Mega Menu offers a variety of beautiful dropdown menu templates which you can attach to your exisiting Shopify navigation, meaning your store could have a menu makeover in minutes.

Related articles

Code

How to implement access control on your Shopify store

Code

How to add extra customer data to Shopify orders

Code

2 ways developers build Shopify storefront apps and how they affect your theme