Type Generation for GraphQL
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 and files 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
We recommend using the Server Preset for a managed workflow. It automatically generates types and files based on your schema without needing extra plugin setup.
The Server Preset generates:
- Resolver types, including parent types, arguments, return values, and context
- Resolver files with types wired up, ready for your business logic
- A resolver map and type definitions to plug into GraphQL servers
This setup expects your schema is split into modules to improve readability and maintainability. For example:
βββ src/
β βββ schema/
β β βββ base/
β β β βββ schema.graphql
β β βββ user/
β β β βββ schema.graphql
β β βββ book/
β β β βββ schema.graphql
Hereβs an example codegen.ts
file using the Server Preset:
import type { CodegenConfig } from "@graphql-codegen/cli";
import { defineConfig } from "@eddeee888/gcg-typescript-resolver-files";
const config: CodegenConfig = {
schema: "src/**/schema.graphql",
generates: {
"src/schema": defineConfig({
resolverGeneration: "minimal",
}),
},
};
export default config;
To generate the resolver types and files, run:
npx graphql-codegen
This creates resolver files like:
import type { QueryResolvers } from "./../../../types.generated";
export const user: NonNullable<QueryResolvers["user"]> = async (
_parent,
_arg,
_ctx,
) => {
// Implement Query.user resolver logic here
};
The user query resolver is typed to ensure that the user resolver expects an id argument and returns a User, giving you confidence and autocomplete while implementing your server logic, which may look like this:
export const user: NonNullable<QueryResolvers["user"]> = async (
parent,
args,
context,
) => {
return context.db.getUser(args.id);
};
See the official Server Preset guide to learn about its other features, including mappers convention and static analysis for runtime safety.
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.
We recommend using the GraphQL Code Generator Client Preset for a managed workflow:
- Write operations with GraphQL syntax in the same file where it is used
- Get type-safety when using the result
Hereβs an example configuration using the Client Preset:
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: "src/**/schema.graphql",
documents: ["src/**/*.ts"],
ignoreNoDocuments: true,
generates: {
"./src/graphql/": {
preset: "client",
config: {
documentMode: "string",
},
},
},
};
export default config;
To keep generated types up to date as you edit your code, run the generator in watch mode:
npx graphql-codegen --config codegen.ts --watch
Once generated, import the graphql
function from src/graphql/
to write GraphQL operations
directly in your TypeScript files:
import { graphql } from "./graphql";
const UserQuery = graphql(`
query User($id: ID!) {
user(id: ID!) {
id
fullName
}
}
`);
const response = await fetch("https://graphql.org/graphql/", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/graphql-response+json",
},
body: JSON.stringify({
query: UserQuery,
variables: { id: "1" },
}),
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
const result: ResultOf<typeof UserQuery> = await response.json();
console.log(result);
For guides on using the Client Preset with popular frameworks and tools, see:
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.