GraphQL Code Generation with TypeScript
Automate type-safe GraphQL development using graphql-codegen to generate TypeScript types, hooks, and resolvers from your schema.
What you'll learn
- ✓How graphql-codegen generates TypeScript types from your schema
- ✓How to generate typed React hooks for queries and mutations
- ✓How to generate server-side resolver type signatures
Prerequisites
- •Basic GraphQL knowledge
- •TypeScript fundamentals
The Problem: Manual Type Duplication
Without code generation, you define types twice: once in your GraphQL schema and again in TypeScript. Every time the schema changes, you manually update your TypeScript interfaces. This leads to drift, runtime errors, and wasted time.
# Schema defines User
type User {
id: ID!
name: String!
email: String!
role: Role!
}
// Manually maintained TypeScript type -- might be out of sync
interface User {
id: string;
name: string;
email: string;
role: 'ADMIN' | 'USER'; // Did someone add EDITOR to the enum?
}
GraphQL Code Generator eliminates this duplication by reading your schema and operations to produce TypeScript types automatically.
Setting Up graphql-codegen
Install the core packages:
npm install -D @graphql-codegen/cli @graphql-codegen/typescript \
@graphql-codegen/typescript-operations \
@graphql-codegen/typescript-resolvers
Create a codegen.ts configuration file:
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: './src/schema/**/*.graphql',
documents: './src/**/*.{ts,tsx}',
generates: {
'./src/generated/graphql.ts': {
plugins: [
'typescript',
'typescript-operations',
],
},
},
};
export default config;
Add scripts to your package.json:
{
"scripts": {
"codegen": "graphql-codegen",
"codegen:watch": "graphql-codegen --watch"
}
}
Run the generator:
npm run codegen
What Gets Generated
The typescript plugin generates types from your schema:
// Generated from schema
export type Role = 'ADMIN' | 'EDITOR' | 'USER';
export type User = {
__typename?: 'User';
id: string;
name: string;
email: string;
role: Role;
};
export type Post = {
__typename?: 'Post';
id: string;
title: string;
content: string;
author: User;
createdAt: string;
};
export type Query = {
__typename?: 'Query';
users: Array<User>;
post?: Post | null;
};
The typescript-operations plugin generates types from your query and mutation documents:
// Your operation
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
// Generated types
export type GetUserQueryVariables = {
id: string;
};
export type GetUserQuery = {
__typename?: 'Query';
user?: {
__typename?: 'User';
id: string;
name: string;
email: string;
} | null;
};
Notice the generated query type only includes the fields you actually selected, not the entire User type. This matches exactly what the API returns.
Generating React Hooks
For React projects with Apollo Client, add the React Apollo plugin:
npm install -D @graphql-codegen/typescript-react-apollo
Update your config:
const config: CodegenConfig = {
schema: './src/schema/**/*.graphql',
documents: './src/**/*.{ts,tsx}',
generates: {
'./src/generated/graphql.ts': {
plugins: [
'typescript',
'typescript-operations',
'typescript-react-apollo',
],
config: {
withHooks: true,
withComponent: false,
},
},
},
};
This generates typed hooks:
// Generated hook
export function useGetUserQuery(
baseOptions: Apollo.QueryHookOptions<GetUserQuery, GetUserQueryVariables>
) {
return Apollo.useQuery<GetUserQuery, GetUserQueryVariables>(
GetUserDocument,
baseOptions
);
}
Use the generated hook in your components:
import { useGetUserQuery } from '../generated/graphql';
function UserProfile({ userId }: { userId: string }) {
const { data, loading, error } = useGetUserQuery({
variables: { id: userId },
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
// data.user is fully typed -- autocomplete works
return (
<div>
<h1>{data?.user?.name}</h1>
<p>{data?.user?.email}</p>
</div>
);
}
Generating Resolver Types
For the server side, generate resolver type signatures:
npm install -D @graphql-codegen/typescript-resolvers
const config: CodegenConfig = {
schema: './src/schema/**/*.graphql',
generates: {
'./src/generated/resolvers-types.ts': {
plugins: ['typescript', 'typescript-resolvers'],
config: {
contextType: '../context#GraphQLContext',
mappers: {
User: '../models/User#UserModel',
Post: '../models/Post#PostModel',
},
},
},
},
};
The contextType option tells codegen about your context shape. The mappers option maps GraphQL types to your database models, handling the difference between what your database returns and what GraphQL exposes.
Use the generated types in your resolvers:
import { Resolvers } from '../generated/resolvers-types';
export const resolvers: Resolvers = {
Query: {
users: async (_, __, context) => {
// context is typed as GraphQLContext
return context.db.users.findAll();
},
post: async (_, { id }, context) => {
// id is typed as string, return type is checked
return context.db.posts.findById(id);
},
},
User: {
// TypeScript knows the parent is UserModel, not the GraphQL User type
posts: async (parent, _, context) => {
return context.db.posts.findByAuthorId(parent.id);
},
},
};
Document Presets: Near-Operation Types
The near-operation-file preset generates types next to the files that use them instead of one large generated file:
const config: CodegenConfig = {
schema: './src/schema/**/*.graphql',
documents: './src/**/*.{ts,tsx}',
generates: {
'./src/': {
preset: 'near-operation-file',
presetConfig: {
extension: '.generated.ts',
baseTypesPath: 'generated/graphql.ts',
},
plugins: ['typescript-operations', 'typescript-react-apollo'],
},
'./src/generated/graphql.ts': {
plugins: ['typescript'],
},
},
};
This creates UserProfile.generated.ts next to UserProfile.tsx, making imports cleaner and keeping generated code co-located with its consumers.
Watch Mode and CI Integration
During development, run codegen in watch mode:
npm run codegen:watch
In CI, run codegen and check for changes to catch schema drift:
# GitHub Actions
- name: Generate types
run: npm run codegen
- name: Check for uncommitted generated files
run: |
git diff --exit-code src/generated/
if [ $? -ne 0 ]; then
echo "Generated files are out of date. Run npm run codegen."
exit 1
fi
Client Preset (Modern Approach)
The newer client preset combines multiple plugins into a single streamlined setup:
npm install -D @graphql-codegen/client-preset
const config: CodegenConfig = {
schema: './src/schema/**/*.graphql',
documents: './src/**/*.{ts,tsx}',
generates: {
'./src/gql/': {
preset: 'client',
config: {
documentMode: 'string',
},
},
},
};
This generates a graphql() function that provides type inference:
import { graphql } from '../gql';
const GET_USER = graphql(`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`);
// TypeScript infers the result type from the query string
const { data } = useQuery(GET_USER, { variables: { id: '1' } });
// data.user.name is typed as string
Handling Custom Scalars
Map custom scalars to TypeScript types:
const config: CodegenConfig = {
schema: './src/schema/**/*.graphql',
generates: {
'./src/generated/graphql.ts': {
plugins: ['typescript'],
config: {
scalars: {
DateTime: 'string',
JSON: 'Record<string, unknown>',
Upload: 'File',
PositiveInt: 'number',
},
},
},
},
};
Summary
GraphQL Code Generator eliminates type duplication between your schema and TypeScript code. Use the typescript plugin for schema types, typescript-operations for query and mutation types, typescript-react-apollo for generated hooks, and typescript-resolvers for server-side resolver signatures. Run codegen in watch mode during development and validate generated files in CI. The modern client preset provides the cleanest developer experience with automatic type inference from query strings.
Related articles
- GraphQL GraphQL Tooling: A Codegen Overview
An overview of GraphQL code generation: what it does, how schemas and operations turn into typed clients, and how to wire it into a modern frontend workflow.
- GraphQL Authentication and Authorization in GraphQL
Implement auth in GraphQL using context-based authentication, custom directives, and field-level authorization patterns.
- GraphQL GraphQL Error Handling Patterns and Extensions
Implement robust error handling in GraphQL with error extensions, union-based errors, and structured error responses for better client DX.
- GraphQL Solving the N+1 Problem in GraphQL with DataLoader
Learn how the DataLoader pattern batches and caches database calls to eliminate N+1 query performance issues in GraphQL APIs.