GraphQL Code Generation and Typing
GraphQL schema is a contract between server and client. GraphQL Code Generator automatically creates TypeScript types from schema and operations, eliminating manual type synchronization and runtime data mismatch errors.
Installing GraphQL Code Generator
npm install -D @graphql-codegen/cli @graphql-codegen/typescript \
@graphql-codegen/typescript-resolvers \
@graphql-codegen/typescript-operations \
@graphql-codegen/typescript-react-apollo \
@graphql-codegen/introspection
Configuration (codegen.yml)
# codegen.yml
overwrite: true
schema: "http://localhost:4000/graphql" # or path to SDL file
documents: "src/**/*.graphql" # client operations
generates:
# Server types (resolvers)
src/generated/graphql-server.ts:
plugins:
- typescript
- typescript-resolvers
config:
contextType: "../context#GraphQLContext"
mappers:
# Map GraphQL types to real DB models
User: "../models/User#UserModel"
Post: "../models/Post#PostModel"
useIndexSignature: true
enumsAsTypes: true
avoidOptionals:
field: true
# Client types (operations + hooks)
src/generated/graphql-client.ts:
plugins:
- typescript
- typescript-operations
- typescript-react-apollo
config:
withHooks: true
withComponent: false
withHOC: false
dedupeFragments: true
# Introspection for IDE
src/generated/introspection.json:
plugins:
- introspection
Server Resolver Types
After generation, Resolvers types match schema exactly:
// src/generated/graphql-server.ts (excerpt)
export type QueryResolvers<ContextType = GraphQLContext> = {
user?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType, RequireFields<QueryUserArgs, 'id'>>;
posts?: Resolver<ResolversTypes['PostConnection'], ParentType, ContextType, Partial<QueryPostsArgs>>;
}
export type UserResolvers<ContextType = GraphQLContext> = {
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
posts?: Resolver<Array<ResolversTypes['Post']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
}
// src/resolvers/user.resolver.ts
import { QueryResolvers, UserResolvers } from '../generated/graphql-server'
import { GraphQLContext } from '../context'
export const userQueryResolvers: QueryResolvers = {
user: async (parent, { id }, ctx: GraphQLContext) => {
// TypeScript knows: id: string, return: UserModel | null
return ctx.db.users.findById(id)
}
}
export const userTypeResolvers: UserResolvers = {
posts: async (parent, args, ctx) => {
// parent is typed as UserModel (from mappers)
return ctx.loaders.postsByUserId.load(parent.id)
}
}
// Assembled resolvers with full typing
export const resolvers = {
Query: userQueryResolvers,
User: userTypeResolvers,
}
Client Types and Hooks
// src/generated/graphql-client.ts (excerpt)
export type GetUserQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;
export type GetUserQuery = {
__typename?: 'Query';
user?: {
__typename?: 'User';
id: string;
name: string;
posts: Array<{
__typename?: 'Post';
id: string;
title: string;
}>;
} | null;
};
export const useGetUserQuery = (baseOptions?: Apollo.QueryHookOptions<GetUserQuery, GetUserQueryVariables>) => {
const options = { ...defaultOptions, ...baseOptions }
return Apollo.useQuery<GetUserQuery, GetUserQueryVariables>(GetUserDocument, options)
}
Running Code Generation
# One-time generation
npx graphql-code-generator
# Watch mode during development
npx graphql-code-generator --watch
Benefits
- Type safety: types match schema exactly
- IDE autocomplete: full IntelliSense for operations
- Breaking changes detected: codegen fails if schema changes incompatibly
- No manual sync: updates when schema changes
Timelines
Setting up code generation with resolvers and client hooks: 1–2 days. Full integration with CI/CD, custom plugins: 2–3 days.







