🎬 That's a Wrap for GraphQLConf 2024! • Watch the Videos • Check out the recorded talks and workshops
DocumentationType Generation

Type Generation for GraphQL Servers

Writing a GraphQL server in JavaScript or TypeScript often involves managing complex types. As your API grows, keeping these types accurate and aligned with your schema becomes increasingly difficult.

Type generation tools automate this process. Instead of manually defining or maintaining TypeScript types for your schema and operations, these tools can generate them for you. This improves safety, reduces bugs, and makes development easier to scale.

This guide walks through common type generation workflows for projects using graphql-js, including when and how to use them effectively.

Why use type generation?

Type generation improves reliability and developer experience across the development lifecycle. It’s especially valuable when:

  • You want strong type safety across your server logic
  • Your schema is defined separately in SDL files
  • Your API surface is large, rapidly evolving, or used by multiple teams
  • You rely on TypeScript for editor tooling, autocomplete, or static analysis

By generating types directly from your schema, you can avoid drift between schema definitions and implementation logic.

Code-first development

In a code-first workflow, the schema is constructed entirely in JavaScript or TypeScript using graphql-js constructors like GraphQLObjectType, GraphQLSchema, and others. This approach is flexible and lets you build your schema programmatically using native language features.

If you’re using this approach with TypeScript, you already get some built-in type safety with the types exposed by graphql-js. For example, TypeScript can help ensure your resolver functions return values that match their expected shapes.

However, code-first development has tradeoffs:

  • You won’t get automatic type definitions for your resolvers unless you generate them manually or infer them through wrappers.
  • Schema documentation, testing, and tool compatibility may require you to export the schema to SDL first.

You can still use type generation tools like GraphQL Code Generator in a code-first setup. You just need to convert your schema into SDL.

To export your schema:

import { printSchema } from 'graphql';
import { schema } from './schema';
import { writeFileSync } from 'fs';
 
writeFileSync('./schema.graphql', printSchema(schema));

Once you’ve written the SDL, you can treat the project like a schema-first project for type generation.

Schema-first development

In a schema-first workflow, your GraphQL schema is written in SDL, for example, .graphql or .gql files. This serves as the source of truth for your server. This approach emphasizes clarity because your schema is defined independently from your business logic.

Schema-first development pairs well with type generation because the schema is serializable and can be directly used by tools like GraphQL Code Generator.

With a schema-first workflow, you can:

  • Generate resolver type definitions that match your schema
  • Generate operation types for client queries, integration tests, or internal tooling
  • Detect breaking changes and unused types through schema diffing tools

Generating resolver types

GraphQL Code Generator can generate resolver scaffolding based on your schema. These types help you implement resolvers with full type safety, including parent types, argument shapes, return values, and context.

Example codegen.ts config:

import type { CodegenConfig } from '@graphql-codegen/cli';
 
const config: CodegenConfig = {
  schema: './schema.graphql',
  generates: {
    './src/generated/resolvers-types.ts': {
      plugins: ['typescript', 'typescript-resolvers'],
    },
  },
};
export default config;

To run the generator:

npx graphql-codegen

This creates a set of resolver types like:

export type QueryResolvers<ContextType = any> = {
  user?: Resolver<User, any, ContextType, RequireFields<QueryUserArgs, 'id'>>;
};

These types ensure that the user resolver expects an id argument and returns a User, giving you confidence and autocomplete while implementing your server logic.

Using generated types in your server

Once generated, you can use these types directly in your resolver map:

import { QueryResolvers } from './generated/resolvers-types';
 
export const queryResolvers: QueryResolvers = {
  user: (parent, args, context) => {
    return context.db.getUser(args.id);
  },
};

You can also extract shared ContextType and Resolver utility types from the generated file and apply them across your codebase.

Generating operation types

In addition to resolver types, you can generate types for GraphQL operations such as queries, mutations, and fragments. This is especially useful for shared integration tests or client logic that needs to match the schema precisely.

Suppose you have a query in ./src/operations/getUser.graphql:

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

Update your codegen config:

const config = {
  schema: './schema.graphql',
  documents: './src/operations/**/*.graphql',
  generates: {
    './src/generated/operations.ts': {
      plugins: ['typescript', 'typescript-operations'],
    },
  },
};

This produces types like GetUserQuery and GetUserQueryVariables, which you can import into your client code or test files.

Typing resolvers manually

If you aren’t ready to introduce type generation, you can still get partial type safety using graphql-js built-in types.

import { GraphQLFieldResolver } from 'graphql';
 
const myResolver: GraphQLFieldResolver<MyParent, MyContext> = (
  parent,
  args,
  context,
  info
) => {
  // ...
};

This pattern may be enough for small projects or static schemas, but it can be hard to maintain and scale without automation.

Best practices for CI and maintenance

To keep your type generation reliable and consistent:

  • Check in generated files to version control so teammates and CI systems don’t produce divergent results.
  • Run type generation in CI to ensure types stay in sync with schema changes.
  • Use schema diffing tools like graphql-inspector to catch breaking changes before they’re merged.
  • Automate regeneration with pre-commit hooks, GitHub Actions, or lint-staged workflows.