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:

GraphQL
schema.graphql
1type Query {
2  books: [Book]
3}
4
5type Book {
6  title: String
7  author: String
8}
9
10type AddBookMutationResponse {
11  code: String!
12  success: Boolean!
13  message: String!
14  book: Book
15}
16
17type Mutation {
18  addBook(title: String, author: String): AddBookMutationResponse
19}

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:

TypeScript
src/index.ts
1// ...other imports
2import { readFileSync } from 'fs';
3
4// Note: this uses a path relative to the project's
5// root directory, which is the current working directory
6// if the server is executed using `npm run`.
7const typeDefs = readFileSync('./schema.graphql', { encoding: 'utf-8' });
8
9interface MyContext {
10  dataSources: {
11    books: Book[];
12  };
13}
14
15const server = new ApolloServer<MyContext>({
16  typeDefs,
17  resolvers,
18});
19
20// ... 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:

Bash
1npm 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:

Bash
1npx graphql-code-generator init

Below is an example of a codegen.yml file:

YAML
1# This configuration file tells GraphQL Code Generator how
2# to generate types based on our schema.
3schema: "./schema.graphql"
4generates:
5  # Specify where our generated types should live.
6  ./src/__generated__/resolvers-types.ts:
7    plugins:
8      - "typescript"
9      - "typescript-resolvers"
10    config:
11      useIndexSignature: true
12      # More on this below!
13      contextType: "../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:

JSON
1{
2// ...
3  "scripts": {
4    "generate": "graphql-codegen --config codegen.yml",
5    "compile": "npm run generate && tsc",
6    "start": "npm run compile && node ./dist/index.js",
7  },
8// ...
9}

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:

TypeScript
resolvers.ts
1// This is the file where our generated types live
2// (specified in our `codegen.yml` file)
3import { Resolvers } from './__generated__/resolvers-types';

You can now add the Resolvers type directly to your resolver map:

TypeScript
1export const resolvers: Resolvers = {}

Your resolvers can now type check that the arguments and return value for each resolver match the schema:

TypeScript
1export const resolvers: Resolvers = {
2  Query: {
3    // TypeScript now complains about the below resolver because
4    // the data returned by this resolver doesn't match the schema type
5    // (i.e., type Query { books: [Book] })
6    books: () => {
7      return "apple";
8    },
9  },
10}

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:

TypeScript
resolvers/queries.ts
1import { QueryResolvers } from '__generated__/resolvers-types';
2
3// Use the generated `QueryResolvers`
4// type to type check our queries!
5const queries: QueryResolvers = {
6  // ...queries
7};
8
9export default queries;
TypeScript
resolvers/mutations.ts
1import { MutationResolvers } from '__generated__/resolvers-types';
2
3// Use the generated `MutationResolvers` type
4// to type check our mutations!
5const mutations: MutationResolvers = {
6  // ...mutations
7};
8
9export 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:

TypeScript
src/index.ts
1export interface MyContext {
2  dataSources: {
3    books: Book[];
4  };
5}
6
7const server = new ApolloServer<MyContext>({
8  typeDefs,
9  resolvers,
10});

Remember the contextType from our codegen.yml file above? You can pass your exported context interface to the contextType configuration option, like so:

YAML
1# ...
2config:
3  useIndexSignature: true
4  # Providing our context's interface ensures our
5  # context's type is set for all of our resolvers.
6
7  # Note, this file path starts from the location of the
8  # file where you generate types.
9  # (i.e., `/src/__generated__/resolvers-types.ts` above)
10  contextType: "../index#MyContext"

Once you regenerate your types, your context value is now automatically typed in all of your resolvers:

TypeScript
1const resolvers: Resolvers = {
2  Query: {
3    // Our third argument (`contextValue`) has a type here, so we
4    // can check the properties within our resolver's shared context value.
5    books: (_, __, contextValue) => {
6      return contextValue.dataSources.books;
7    },
8  },
9}

Basic runnable example

Check out our example using Apollo Server with generated types on CodeSandbox:

Edit in CodeSandbox

See GraphQL Code Generator's docs for further guidance on the different features and integrations it supports.

Feedback

Edit on GitHub

Forums