GraphQL Codegen: What is it?
Photo by Pablo Titosse on Unsplash
At EF Go Ahead Tours Engineering, we use a tool called graphql-codegen
often. Since we use TypeScript and GraphQL, it saves us a lot of time since we can create TypeScript types from our GraphQL schema. Let's set up a basic GraphQL schema to show how it works:
1const typeDefs = gql`2 type Query {3 cart(id: ID!): Cart4 carts: [Cart!]5 }6 7 type Cart {8 items: [Product!]9 grandTotal: Int!10 }11
12 type Product {13 sku: String!14 price: Int!15 }16`
Pretty simple, we have a two queries and a basic eCommerce model. This sets up our contract with our API. Let's generate our application types. We can do this by installing the needed packages and the plugins for typescript:
1npm i @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers
All of these packages gives us the functionality to generate both types and a typed resolver, with a pretty minimal configuration, as specified in a YAML file. Let's set one up:
1schema: "./src/typeDefs.ts"2generates:3 src/generated/types.ts4 plugins:5 - typescript6 - typescript-resolvers
This is an incredibly barebones configuration, only meant to show the TypeScript type generation, more details about configuration can be found here.
All that's left is to run our generator and see what types we are able to use!
1npx graphql-codegen
this can also be a package.json
script:
1 "scripts": {2 "generate": "graphql-codegen"3 }
at which point the command can be run with npm run generate.
After this, we can see what types are generated in the ./src/generated/typeDefs.ts
file! Having this tool is incredibly helpful for both back end and front end work, since our types can be generated from our GraphQL schema, which introduces coupling of the schema definition and the types that we can use within our apps. This makes GraphQL schemas dual-channel, which means that the data and the types of that data is now available for us to use in TypeScript!
Server and client side generation
graphql-codegen works for both server and client side. Let's implement a basic resolver using our newly generated types:
1import { Resolvers } from "./src/generated/types.ts"2
3export const resolvers: Resolvers => {4 Query: {5 cart(parent, args, context) {6 console.log(args)7 return null8 },9 carts(parent, args, context) {10 console.log(args)11 return []12 }13 }14}
Here, we import the Resolvers
type from our generated schema, which gives us typechecking for all of our resolvers! This also includes typechecking for our parent type, our field arguments, and our context. Super easy! The package also splits out each individual resolver so one could do:
1// ./src/cartResolvers.ts2import { CartResolvers } from "./src/generated/types.ts"3
4export const cartResolvers: CartResolvers => {5 cart(parent, args, context) {6 console.log(args)7 return null8 },9 carts(parent, args, context) {10 console.log(args)11 return []12 }13}14
15export const resolvers: Resolvers => {16 Query: {17 ...cartResolvers18 }19}
This makes creating typechecked resolvers with IntelliSense in VS Code very easy and guarantees static types from your schema. How can this tool also help us with the front end? After all, one of the benefits of using GraphQL is that we can query only the data we need. We can do this with a very minimal configuration update in our client side app, copying it from our GraphQL server configuration:
1schema: "./src/typeDefs.ts"2generates:3 src/generated/types.ts4 plugins:5 - typescript6 - typescript-resolvers7+ - typescript-react-apollo8+ - typescript-operations9+ - fragment-matcher10+ documents: "src/graphql/**.{ts, tsx, graphql}"
This configuration will scrape all of our graphql, ts, and tsx files in our src/graphql/
directory and generate types in the src/generated/types.ts
file. We'll also need to install the extra plugins that this config requires:
1npm i @graphql-codegen/typescript-operations @graphql-codegen/fragment-matcher @graphql-codegen/typescript-react-apollo
This will allow us to do three things:
generate react apollo hooks for our queries and mutations
match our fragments to our types
generate typescript types for our queries and mutations
At EF Go Ahead Tours we use the first one quite a bit in most typical front-end applications, since we leverage a ton of GraphQL. A typical React component that fetches a cart by ID from our GraphQL server with a search parameter would look like:
1// src/graphql/cart.graphql2
3query FindCart($id: ID!) {4 cart(id: $id) {5 items6 grandTotal7 }8}9 10// src/checkout/cart.tsx11import React from "react";12import { useFindCart } from "./src/generated/typedefs.ts";13import { Item } from "./Item";14import { Spinner } from "../shared/Spinner";15
16export const Cart: React.FC = () => {17 const cartId = new URLSearchParams(window.location.search).get("cartId");18 const { data, loading } = useFindCart({19 variables: {20 id: cartId ?? ""21 }22 });23
24 if (loading) {25 return <Spinner />26 }27
28 return (29 <>30 <h2>{data.grandTotal}</h2>31 {data?.items.map((item) => <Item item={item} />32 </>33 );34}
Where all of our queries and mutations, along with their variables are typechecked. This makes it very easy to build services alongside clients that consume those services, while maintaining type checking between the both of them. Graphql-codegen is an invaluable tool for us developers here at EF Go Ahead Tours. Without it we would never have been able to build services and front end applications with the speed of sharing types between client and server without the manual work of migrating types and the developer experience that using graphql-codegen affords us.