Integrating GraphQL Codegen + React Query + Clerk JWT Tokens

robertoyamanaka

Roberto Yamanaka

Posted on April 3, 2024

Integrating GraphQL Codegen + React Query + Clerk JWT Tokens

Use Case

So, you are using GraphQL Codegen to automatically generate your GraphQL schema (and types) based on your GraphQL schema in the backend.

Using Codegen gives you a better experience, since it automatically generates you the types and custom code/hooks you want. You can even automatically generate your React Query Hooks to fetch the GraphQL data. Life is great.

However... there is one issue. How do these automatically generated hooks and functions handle the authentication?

Let's dive into how we can achieve this: authentication + generated code

Basic setup

We'll start by creating a new NextJS project (vanilla React works just fine).

I'll name the project codegen-jwt and use the default configuration from Next

What is your project named? my-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like to use `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to customize the default import alias (@/*)? No / Yes
What import alias would you like configured? @/*
Enter fullscreen mode Exit fullscreen mode

Next, lets install GraphQL, Codegen with some useful plugins and Tanstack React Query to test our generated query hooks.

npm i graphql axios @tanstack/react-query
npm i -D typescript ts-node @graphql-codegen/cli @graphql-codegen/client-preset @parcel/watcher @graphql-codegen/typescript-react-query
Enter fullscreen mode Exit fullscreen mode

Now lets add our Auth provider Clerk. You can follow these instructions for a quick setup.

npm install @clerk/nextjs
Enter fullscreen mode Exit fullscreen mode

Prepare the GraphQL Queries and source

For this tutorial we will use a public GraphQL schema for our Codegen

https://graphql.anilist.co
Enter fullscreen mode Exit fullscreen mode

Now let's create some GraphQL queries that Codegen will take to generate the types for us.

Create a new file called codegen/queries.graphql and add this query.

query findAnimeById($id: Int) {
  Media (id: $id, type: ANIME) {
    id
    title {
      romaji
      english
      native
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Codegen will use this to generate the types and the react-query hooks.

Building our Code Generator

Ok! Now that we have the setup ready. Let's write our codegen/codegen.ts file. This serves as the Instructions for Codegen.

import type { CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
  overwrite: true,
  schema: "https://graphql.anilist.co",
  documents: 'codegen/**/*.graphql',
  generates: {
    "graphql/__generated__/graphql.ts": {
      plugins: [
        "typescript",
        "typescript-operations",
        "typescript-react-query",
      ],
      config: {
        reactQueryVersion: "auto",
        exposeQueryKeys: true,
        exposeFetcher: true,
        fetcher: {
          func: "@/codegen/fetcher#useFetchGraphQLData",
          isReactHook: true,
        },
      },
    },
  },
};

export default config;
Enter fullscreen mode Exit fullscreen mode

The most important part of this is the fetcher config. This is where we define all the configuration for the fetcher Codegen will use.

Our fetcher.tsx has a custom hook that will handle the calls to the backend. Here we will pass our Clerk JWT Token to the Headers to authenticate using the useAuth hook.

If your auth provider doesn't use hooks for obtaining the JWT Token, you can create a simple Ts function to obtain it and then add it to your headers. Just set isReactHook: false

Here is that codegen/fetcher.tsx file.

import { useAuth } from "@clerk/nextjs";
import axios, { AxiosError, isAxiosError } from "axios";

interface GraphQLResponse<T> {
  data: T;
  errors?: { message: string }[];
}

export const useFetchGraphQLData = <TData, TVariables>(
  query: string
): ((variables?: TVariables) => Promise<TData>) => {
  const { getToken } = useAuth();
  const url =
    "https://dummyjson.com"; // Replace this with your GraphQL API URL

  return async (variables?: TVariables) => {
    const token = await getToken();

    try {
      const response = await axios.post<GraphQLResponse<TData>>(
        url,
        {
          query,
          variables,
        },
        {
          headers: {
            "Content-Type": "application/json",
            Authorization: token ? `Bearer ${token}` : "",
          },
        }
      );

      if (response.data.errors) {
        throw new Error(response.data.errors[0].message);
      }

      return response.data.data;
    } catch (error) {
      if (isAxiosError(error)) {
        const serverError = error as AxiosError<GraphQLResponse<unknown>>;
        if (serverError && serverError.response) {
          const errorMessage =
            serverError.response.data.errors?.[0]?.message ||
            "Error in GraphQL request";
          throw new Error(errorMessage);
        }
      }
      throw error;
    }
  };
};

Enter fullscreen mode Exit fullscreen mode

And just like that you can add JWT tokens to your graphQL queries!

Running Codegen

Now that we have everything in place, lets generate the codegen with this command.

npx graphql-codegen --config codegen/codegen.ts 
Enter fullscreen mode Exit fullscreen mode

If we go to the generated file graphql/__generated__/graphql.ts we can see that it automatically created the React Query Hook.

export const useFindAnimeByIdQuery = <
      TData = FindAnimeByIdQuery,
      TError = unknown
    >(
      variables?: FindAnimeByIdQueryVariables,
      options?: Omit<UseQueryOptions<FindAnimeByIdQuery, TError, TData>, 'queryKey'> & { queryKey?: UseQueryOptions<FindAnimeByIdQuery, TError, TData>['queryKey'] }
    ) => {

    return useQuery<FindAnimeByIdQuery, TError, TData>(
      {
    queryKey: variables === undefined ? ['findAnimeById'] : ['findAnimeById', variables],
    queryFn: useFetchGraphQLData<FindAnimeByIdQuery, FindAnimeByIdQueryVariables>(FindAnimeByIdDocument).bind(null, variables),
    ...options
  }
    )};
Enter fullscreen mode Exit fullscreen mode

Testing it out

Let's do a quick setup for Clerk following the Quickstart Guide. You will have all the code in the Repo so don't worry.

And also add the QueryClientProvider from React Query

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const queryClient = new QueryClient()

  return (
    <ClerkProvider>
      <QueryClientProvider client={queryClient}>
        <html lang="en">
          <body className={inter.className}>{children}</body>
        </html>
      </QueryClientProvider>
    </ClerkProvider>
  );
}

Enter fullscreen mode Exit fullscreen mode

Now we can create a dummy page app/test-codegen/page.tsx to test our generated hook

"use client"
import { useFindAnimeByIdQuery } from "@/graphql/__generated__/graphql";

export default function TestCodegen() {
  const { data, isLoading } = useFindAnimeByIdQuery({ id: 15125 });

  if (isLoading) {
    return <div>Loading...</div>;
  }
  return (
    <div className="h-screen flex items-center justify-center">
      <p>Check your network</p>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Once we load the page we get our JWT Token sent as the Bearer token!

Sending the JWT Token to the backend

GraphQL Query

There we have it! Now you can send your JWT Token or customize your generated requests to your GraphQL API URL.

Here is the link to the repo.

See you next time!

💖 💪 🙅 🚩
robertoyamanaka
Roberto Yamanaka

Posted on April 3, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related