Cover image

How to use GraphQL Codegen with Payload and React Query in Nextjs

GraphQL is a very powerful API but at times it can also be unwieldy if you have lots of queries and complex data structures to fetch, and that's where code generators come in. A code generator will read your .graphql files and then compare them with the API's schema to validate them, after which it will generate all the types, queries, mutations and hooks for a fetching library of your choice. Jump to the end if you want to see the codebase.

This article is part of a series of working with the Payload APIs:

Dependencies

Here's the full list of dependencies:

  • @graphql-codegen/cli
  • @graphql-codegen/client-preset
  • @graphql-codegen/introspection
  • @graphql-codegen/typescript-react-query
  • graphql
  • @tanstack/react-query

Folder structure

You can structure your directory however you prefer, for this example we're going to put everything inside the src folder and contain all of our GraphQL files in graphql.

  • src/

    • graphql/

      • generated/ <- generated files
      • queries/
      • fragments/
      • mutations/

Install dependencies

1yarn add @tanstack/react-query graphql
bash

Install the dev dependencies

1yarn add -D @graphql-codegen/cli @graphql-codegen/client-preset @graphql-codegen/introspection @graphql-codegen/typescript-react-query
bash

Codegen

Let's start with the codegen config in our root directory:

1// codegen.ts
2require('dotenv').config()
3import type { CodegenConfig } from '@graphql-codegen/cli'
4
5const config: CodegenConfig = {
6 overwrite: true,
7 schema: process.env.NEXT_PUBLIC_PAYLOAD_API_URL,
8 documents: 'src/graphql/**/*.graphql',
9 generates: {
10 'src/graphql/generated/client.ts': {
11 plugins: ['typescript', 'typescript-operations', 'typescript-react-query'],
12 config: {
13 exposeQueryKeys: true,
14 exposeFetcher: true,
15 withHooks: true,
16 dedupeFragments: true,
17 fetcher: {
18 endpoint: process.env.NEXT_PUBLIC_PAYLOAD_API_URL,
19 fetchParams: {
20 headers: {
21 'content-type': 'application/json',
22 },
23 },
24 },
25 },
26 },
27 },
28}
29
30export default config
typescript

Writing queries

Now we're ready to add some fragments and queries so we can begin to generate our hooks.

First create your fragments for each type content, you can also create variations of your fragments by naming them something else for example you may have Post and PostCard and the latter of which would contain less fields.

For now we'll keep it simple with just the Post and then sub fragments for each collection type.

1fragment Post on Post {
2 title
3 content
4 tags {
5 ...Tag
6 }
7 category {
8 ...Category
9 }
10 author {
11 ...Author
12 }
13}
14
graphql

Our queries will look a bit different, you need to name and you can even provide variables for custom or the generic inputs from the graph, the easiest way to find all the specific types available is to browse your schema in the provided GraphQL playground.

1query AllPosts($where: Post_where) {
2 Posts(where: $where) {
3 docs {
4 ...Post
5 }
6 totalDocs
7 }
8}
9
graphql

Now it's time to generate our hooks. You can add this command to your package.json as we've done in the repo and you can add a --watch flag in order to have it run automatically on file changes.

1yarn graphql-codegen --config codegen.ts
bash

Using the hooks in Nextjs

We're nearly there, I promise. We need to set up the query client in _app.tsx to begin using our hooks and let's set up the hydration layer as well so we can take advantage of server side rendering too:

1import '@/styles/globals.css'
2import type { AppProps } from 'next/app'
3import { QueryClient, QueryClientProvider, Hydrate } from '@tanstack/react-query'
4
5const queryClient = new QueryClient()
6
7export default function App({ Component, pageProps }: AppProps) {
8 return (
9 <QueryClientProvider client={queryClient}>
10 <Hydrate state={pageProps.dehydratedState}>
11 <Component {...pageProps} />
12 </Hydrate>
13 </QueryClientProvider>
14 )
15}
16
tsx

Frontend use

This is the easiest way to use the hooks, simply import them (your IDE should auto suggest the hooks as named by each query) and then you can use it directly in your components. This is the exact same hook as a normal useQuery so you get all the benefits of caching, methods and can expose other functions like refetch if you need to.

And that's it! You should now see your content coming through.

1import { useAllPostsQuery } from '@/graphql/generated/client'
2
3// ..
4
5export default function Home() {
6 const { data } = useAllPostsQuery()
7 return (
8 <>
9 <Head>
10 <title>Create Next App</title>
11 <meta name='description' content='Generated by create next app' />
12 <meta name='viewport' content='width=device-width, initial-scale=1' />
13 <link rel='icon' href='/favicon.ico' />
14 </Head>
15 <main className={styles.main}>
16 <article>
17 <h1>All posts</h1>
18 {data?.Posts?.docs?.map((post, index) => {
19 return (
20 <article key={index}>
21 <h1>{post?.title}</h1>
22 </article>
23 )
24 })}
25 </article>
26 </main>
27 </>
28 )
29}
tsx

Server side fetching

Here you have a few options, one is to destructure your hook via the provided helpers getKey and fetcher so that we can call them manually, the other option is to use the dehydration state.

I've provided comments for the first approach, should you wish to have your frontend entirely static, but in the live code we're using the dehydrated state so that our frontend can hydrate our data and then update it behind the scenes, which would be very useful if you're building a dashboard or an app that uses live data.

1// index.tsx
2
3import { useAllPostsQuery } from '@/graphql/generated/client'
4import { dehydrate, QueryClient } from '@tanstack/react-query'
5
6// ...
7
8export async function getStaticProps() {
9 /*
10 If you want to use props, then this is how you would destructure the hook query and `data` will be the end result:
11
12 const data = await useAllPostsQuery.fetcher().call({})
13 const posts = data.Posts?.docs
14 */
15 const fetchAllPosts = useAllPostsQuery.fetcher()
16 const queryClient = new QueryClient()
17
18 await queryClient.prefetchQuery(useAllPostsQuery.getKey(), fetchAllPosts)
19
20 return {
21 props: {
22 //data: posts, <- pass our data directly as props
23 dehydratedState: dehydrate(queryClient),
24 },
25 revalidate: 78000,
26 }
27}
28
tsx
Screenshot of the rendered posts

Types

All of your fragments are available as types too so you can import them and then use them to create components that take that data in as props:

1import { PostFragment } from '@/graphql/generated/client'
2
3// ...
4
5// below is an ouput of how our PostFragment has been generated:
6
7type PostFragment = {
8 __typename?: "Post" | undefined;
9 title?: string | null | undefined;
10 content?: any | null;
11 tags?: {
12 __typename?: "Tag" | undefined;
13 name?: string | null | undefined;
14 }[] | null | undefined;
15 category?: {
16 ...;
17 } | ... 1 more ... | undefined;
18 author?: {
19 ...;
20 } | ... 1 more ... | undefined;
21}
typescript

What about mutations?

They're just as simple, although in this example project we don't use it, here is an example of the like button on our own website. Mutations will expose a mutate and mutateAsync function to cover a variety of your use cases.

Note that in this example we're using a custom mutation of ours, something we'll cover in another article.

1mutation SendLike($articleId: String!) {
2 AddLikeToArticle(articleId: $articleId) {
3 ...ArticleLikeCount
4 }
5}
6
graphql
1import { useSendLikeMutation } from '@graphql/generated/client'
2
3// ...
4
5const { mutateAsync } = useSendLikeMutation({})
6
7// ...
8
9mutateAsync({ articleId: articleId })
tsx

Things to note

There are caveats with code generators that you should be aware of, in particular with typings and some of the uncertainty it has, so a lot of your fields are going to be wrapped in a Maybe which means it might not exist regardless of your field structure. We recommend this video by Matt Pocock on code generators with typescript.

You can replace the default fetcher library with something else like graphql-request, we recommend reading through the documentation to figure out a setup that works best for you.

This configuration can be set up with other libraries instead of react-query such as urql.

Wrap up

That's enough of a breakdown, the full repo is available here for inspection.

We'll continue with tutorials around GraphQL, next up is extending Payload with custom queries and mutations, follow us on Twitter to stay up to date when that releases.