How to configure GraphQL-Codegen and React Apollo Client to do work for you

Jack Gardner
The Startup
5 min readMay 8, 2020

--

I’m writing this as I am part-way through my 4th React / React Native app which uses Apollo Client, and I’ve just noticed how much time I have saved by configuring GraphQL-Codegen correctly. By the end of this article, you should hopefully know how to generate TypeScript types and enums for your GraphQL types and type-safe React hooks for your queries and mutations. This will lead to a consistent and reliable way of working with data throughout your app.

GraphQL-Codegen Setup

I am going to assume that you have a GraphQL backend already setup and running. In my latest project I’m thoroughly enjoying Hasura and Nhost, but you could choose GraphQL Yoga with Prisma, Graphcool or whatever you choose. You just need the endpoint and any auth credentials.

First you will need to install the required GraphQL-Codegen dependencies:

yarn add --dev @graphql-codegen/cli @graphql-codegen/introspection @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo

And in your package.json, add a couple of scripts to make your life easier:

"scripts": {
"generate": "graphql-codegen --config codegen.js -r dotenv/config .env",
"generate:watch": "yarn generate --watch"
}

You will probably also want to add yarn generate && to your build command to ensure you build your latest model set before building your app.

The generate function will pass in any environment variables from your .env file, and use the config file codegen.js, which we are just about to create. It’s important to keep your backend’s hostname and any secrets in a .env file as you may use multiple environments and shouldn’t commit secrets to your repo. I’ve chosen to use a JS file for the config as I find it to be the most readable, and it also supports comments, but you can use a YAML file or JSON.

You will run the watch command in the background while developing. Not only does this generate stuff for you, but it also checks your GraphQL logic against your backend as you are writing it and gives really handy error messages, so you will catch errors early.

This is where the magic happens

codegen.js

As you can see, this shows how the environment variables are added and used in the config. Note: If you don’t recognise the syntax, I’m using the computed property names syntax to use the API_URL env variable as on object key.

The documents key indicates the input files to use to generate the output. We are going to write all our GraphQL in .graqhql files within the ./api directory.

GraphQL-Codegen is then going to generate the ./api/index.ts file. This means when we are using the generated code, we can import it from the root api directory.

We do not want HOCs or Components as they are soooo last year, hooks all the way.

The final thing to note is that we need to use the apolloReactHooksImportFrom key if we want to use @apollo/client version 3 beta, which we do, obviously.

Note: I have left in the required Hasura headers to show how you are able to add headers to the request, but you can ignore them if you are not using Hasura. If you are using Hasura, you are able to change the X-Hasura-Role schema to generate GraphQL for different roles, which I think is really cool.

Our new workflow

When working with the backend now, we will solely be using .graphql and the generated hooks. There are 3 types of GraphQL file we will make - Fragments, Queries and Mutations.

Naming convention

The way I like to name these files is:

[typeName].[mutationType?].[fragment | query | mutation].graphql

Examples:

image.fragment.graphql
post.fragment.graphql
post.query.graphql
posts.query.graphql
user.fragment.graphql
user.query.graphql
user.create.mutation.graphql
user.delete.mutation.graphql

Fragments

Fragments are reusable units of GraphQL Types. When you create a Fragment, you defining a set of fields that belong to a certain Type.

For each GraphQL Type you are going to be using in the front end, I’d like you to create a Fragment. This is because without creating a Fragment, Codegen will not generate & export an easy-to-use TypeScript Type, and instead just use an ugly TypeScript Omit. The below code will generate an ImageFragment, a UserFragment and a PostFragment .

Example fragments

As you can see, you can “nest” fragments do minimise duplication, and to make them easier to work with in TypeScript. For example, with the fragments above, you may have an <Image />component which accepts an ImageFragment as a prop. If use the queries below, the data is fully typed throughout and you can pass in the user.avatar , post.image and post.featuredImage straight to the <Image /> component, as TypeScript automatically recognises them as ImageFragments.

Queries and Mutations

Note: The format for your queries and mutations will obviously depend on your GraphQL server.

Example query & mutation

Queries are super easy to write in GraphQL, but if you do everything manually (as I did in my last project 😩), typing the Apollo query hooks can get rather annoying. So what’s the output of this when put through the Codegen?

Well, you get a bunch of Types which you can import, which is pretty handy. You also get hooks! usePostQuery() and usePostLazyQuery() which let you pass in the id and out comes your typed data. Here’s an example component:

Example Component using usePostQuery()

No need to pass the generics into the useQuery() hook as that’s all done for you. data is fully typed, and variables are checked and have IntelliSense suggestions. All that from writing a very simple .graphql file.

Other Benefits

  • Backend and Frontend Sync: When you make a change to the backend, the Codegen can spot any breaking changes instantly. For example, changing the name of a field, changing the type of a field used in a mutation, or changing permissions so fields aren’t “visible” to that user, will throw an error instantly.
  • Less typing means stronger Typing?!: The types are generated automatically from the GraphQL endpoint, which means you don’t make mistakes when writing the types. The less code you write, the less likely it is you will make a mistake!
  • CI / CD: This can easily be integrated into your CI/CD workflow as an additional way to test and spot bugs in new environments on push.
  • Monorepo: It’s easy to separate out this logic into a separate package in a monorepo to reuse your api client across multiple projects.

Conclusion

My aim when creating new apps is to make the code as simple and readable as possible, while keeping bugs to a minimum. This structure has really helped me in achieving that goal.

Please let me know in the comments if you have any questions or tips, and share if you think it is interesting or will help others 🙂

--

--

Jack Gardner
The Startup

Full Stack TypeScript Developer | Specialising in React Native & GraphQL | Co-founder @ ethy.co.uk