Generating types from a GraphQL schema
How to ensure your resolvers are type safe
👋 If you haven't set up a TypeScript project using Apollo Server yet, follow our Getting Started guide before continuing.
GraphQL uses a type system to clearly define the available data for each type and field in a GraphQL schema. Type generation libraries can take advantage of the strongly-typed nature of a GraphQL schema to automatically generate TypeScript types based on that schema.
You can use these generated TS types in your resolvers to type-check that your resolvers' return values match the field types dictated by your schema. Type checking your resolvers enables you to catch errors quickly and gives you the peace of mind that type safety ensures.
Looking to generate types for your Apollo Federation subgraphs? Our Subgraph template lays down the groundwork so you can quickly set up a subgraph with generated types.
Setting up your project
We'll use the GraphQL Code Generator library to generate types based on our GraphQL schema. There are multiple ways to provide a schema to GraphQL Code Generator. Below, we'll show the most common method, which requires our schema to be in a .graphql
file.
If you haven't already, move your server's schema into a .graphql
file, like so:
type Query {books: [Book]}type Book {title: Stringauthor: String}type AddBookMutationResponse {code: String!success: Boolean!message: String!book: Book}type Mutation {addBook(title: String, author: String): AddBookMutationResponse}
If you moved your schema into a .graphql
file, update your imports to ensure you're still properly passing your schema to your server. In the file where you create your server, you can read in your schema using readFileSync
from the fs
package:
// ...other importsimport { readFileSync } from 'fs';// Note: this uses a path relative to the project's// root directory, which is the current working directory// if the server is executed using `npm run`.const typeDefs = readFileSync('./schema.graphql', { encoding: 'utf-8' });const server = new ApolloServer<MyContext>({typeDefs,resolvers,});// ... start our server
Restart your server to ensure it can find and use your schema and that everything works as expected. Next, we'll install the packages we need to generate types automatically based on our schema.
Installing and configuring dependencies
Run the following command to install the @graphql-codegen/cli
, @graphql-codegen/typescript
, and @graphql-codegen/typescript-resolvers
packages into your project's dev dependencies:
npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers
For more information on each package above, check out the GraphQL Code Generator docs.
Next, we'll set up a configuration file to tell GraphQL Code Generator where and how to generate types. You can do this by manually creating a codegen.yml
file or by using the following command, which walks you through the process:
npx graphql-code-generator init
Below is an example of a codegen.yml
file:
# This configuration file tells GraphQL Code Generator how# to generate types based on our schema.schema: "./schema.graphql"generates:# Specify where our generated types should live../src/__generated__/resolvers-types.ts:plugins:- "typescript"- "typescript-resolvers"config:useIndexSignature: truecontextType: "../index#MyContext"
See the docs for more information on the above configuration options.
Finally, we recommend adding helpful scripts to your package.json
file to ensure your TS types are regularly generated:
{// ..."scripts": {"generate": "graphql-codegen --config codegen.yml","compile": "npm run generate && tsc","start": "npm run compile && node ./dist/index.js",},// ...}
We also recommend adding scripts to watch your code, enabling your types to regenerate and your TypeScript files to recompile in the background as you work.
Above, running the npm start
command generates types based on our GraphQL schema and compiles our TypeScript code. The first time you run the graphql-codegen
command, you'll see a file full of generated types at the path you specified in your codegen.yml
file.
Adding types to resolvers
The typescript-resolvers
plugin creates a Resolvers
type that you can use to add a type to your resolver map, ensuring your resolvers return values match the field types specified by your schema.
Import the Resolvers
type into the file where you define your resolvers:
// This is the file where our generated types live// (specified in our `codegen.yml` file)import { Resolvers } from './__generated__/resolvers-types';
You can now add the Resolvers
type directly to your resolver map:
export const resolvers: Resolvers = {}
Your resolvers can now type check that the arguments and return value for each resolver match the schema:
export const resolvers: Resolvers = {Query: {// TypeScript now complains about the below resolver because// the data returned by this resolver doesn't match the schema type// (i.e., type Query { books: [Book] })books: () => {return "apple";},},}
If your resolvers are in multiple files, you can pull out the corresponding generated types for the resolvers into those files. For example, below, we import the generated types into the separate files we have for our queries and mutations:
import { QueryResolvers } from '__generated__/resolvers-types';// Use the generated `QueryResolvers`// type to type check our queries!const queries: QueryResolvers = {Query: {// ...queries},};export default queries;
import { MutationResolvers } from '__generated__/resolvers-types';// Use the generated `MutationResolvers` type// to type check our mutations!const mutations: MutationResolvers = {Mutation: {// ...mutations},};export default mutations;
Context typing for resolvers
You can also configure GraphQL Code Generator to add a type for the context your resolvers share, ensuring TypeScript warns you if you attempt to use a value that doesn't exist.
To do this, you must first export the interface you pass to Apollo Server as a generic type parameter for typing your context value:
export interface MyContext {dataSources: {books: Book[];};}const server = new ApolloServer<MyContext>({typeDefs,resolvers,});
Remember the contextType
from our codegen.yml
file above? You can pass your exported context interface to the contextType
configuration option, like so:
# ...config:useIndexSignature: true# Providing our context's interface ensures our# context's type is set for all of our resolvers.# Note, this file path starts from the location of the# file where you generate types.# (i.e., `/src/__generated__/resolvers-types.ts` above)contextType: "../index#MyContext"
Once you regenerate your types, your context value is now automatically typed in all of your resolvers:
const resolvers: Resolvers = {Query: {// Our third argument (`contextValue`) has a type here, so we// can check the properties within our resolver's shared context value.books: (_, __, contextValue) => {return contextValue.dataSources.books;},},}
Basic runnable example
Check out our example using Apollo Server with generated types on CodeSandbox:
See GraphQL Code Generator's docs for further guidance on the different features and integrations it supports.