GraphQL Code Generation â TypeScript Types from Schema
In this tutorial, you will learn about GraphQL Code Generation. We cover key concepts, practical examples, and best practices to help you master this topic.
GraphQL Code Generator (graphql-codegen) automatically produces TypeScript types, React hooks, and resolver types from your GraphQL schema, eliminating manual type definitions.
What You'll Learn
You will learn how to configure graphql-codegen, generate TypeScript types for server and client, generate React hooks with Apollo, and create custom plugins.
Why Code Generation Matters
Manually writing TypeScript types for GraphQL operations is error-prone and tedious â every schema change requires updating types in multiple places. Codegen reads your schema and .graphql operation files and generates exact types automatically. DodaTech's engineering team uses codegen to generate types for all 200+ GraphQL operations in the Durga Antivirus Pro dashboard, catching type mismatches at build time instead of runtime.
flowchart LR
A["schema.graphql\n(SDL)"] --> B["GraphQL Codegen\n(graphql-codegen)"]
C["operations.graphql\n(queries, mutations)"] --> B
B --> D["types.ts\n(TypeScript types)"]
B --> E["hooks.ts\n(React hooks)"]
B --> F["resolvers.ts\n(Resolver types)"]
style B fill:#dbeafe,stroke:#2563eb
style D fill:#fef3c7,stroke:#d97706
style E fill:#fef3c7,stroke:#d97706
Prerequisites: TypeScript, GraphQL schema, and React familiarity.
Installation and Configuration
npm install -D @graphql-codegen/cli @graphql-codegen/typescript
npm install -D @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo
# codegen.yml
schema: './src/schema/**/*.graphql'
documents: './src/**/*.graphql'
generates:
src/generated/graphql.ts:
plugins:
- typescript
- typescript-operations
- typescript-react-apollo
config:
withHooks: true
withHOC: false
withComponent: false
apolloReactCommonImportFrom: '@apollo/client'
# Generate types
npx graphql-codegen --config codegen.yml
# Watch for changes
npx graphql-codegen --config codegen.yml --watch
Generated Types
# Input schema (schema.graphql)
enum Severity { LOW MEDIUM HIGH CRITICAL }
type Threat {
id: ID!
name: String!
severity: Severity!
detectedAt: DateTime!
deviceId: ID!
}
type Query {
threats(severity: Severity): [Threat!]!
threat(id: ID!): Threat
}
// Generated types (graphql.ts)
export enum Severity {
Low = 'LOW',
Medium = 'MEDIUM',
High = 'HIGH',
Critical = 'CRITICAL',
}
export type Threat = {
__typename?: 'Threat';
id: Scalars['ID'];
name: Scalars['String'];
severity: Severity;
detectedAt: Scalars['DateTime'];
deviceId: Scalars['ID'];
};
export type Query = {
__typename?: 'Query';
threats?: Maybe<Array<Threat>>;
threat?: Maybe<Threat>;
};
export type QueryThreatsArgs = {
severity?: InputMaybe<Severity>;
};
Generating React Hooks
# operations.graphql
query GetCriticalThreats($severity: Severity!) {
threats(severity: $severity) {
id
name
severity
detectedAt
}
}
mutation DismissThreat($id: ID!) {
dismissThreat(id: $id) {
id
status
}
}
// Generated hooks
export function useGetCriticalThreatsQuery(
options: Omit<QueryHookOptions<GetCriticalThreatsQuery>, 'variables'> & {
variables: GetCriticalThreatsQueryVariables;
}
) {
return useQuery<GetCriticalThreatsQuery, GetCriticalThreatsQueryVariables>(
GetCriticalThreatsDocument,
options
);
}
export function useDismissThreatMutation(
options?: MutationHookOptions<DismissThreatMutation, DismissThreatMutationVariables>
) {
return useMutation<DismissThreatMutation, DismissThreatMutationVariables>(
DismissThreatDocument,
options
);
}
// Usage in React component
import { useGetCriticalThreatsQuery, useDismissThreatMutation, Severity } from './generated/graphql';
function ThreatDashboard() {
const { data, loading, error } = useGetCriticalThreatsQuery({
variables: { severity: Severity.Critical },
});
const [dismissThreat] = useDismissThreatMutation();
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return (
<div>
{data?.threats?.map(threat => (
<ThreatCard
key={threat.id}
name={threat.name}
severity={threat.severity}
onDismiss={() => dismissThreat({ variables: { id: threat.id } })}
/>
))}
</div>
);
}
Server-Side Resolver Types
# codegen.yml â server config
schema: './src/schema/**/*.graphql'
generates:
src/generated/resolvers.ts:
plugins:
- typescript
- typescript-resolvers
config:
contextType: '../context#Context'
useIndexSignature: true
mapping:
DateTime: Date
// Generated resolver types
import { Context } from '../context';
export type Resolvers = {
Query?: {
threats?: Resolver<Maybe<Array<Threat>>, {}, Context, QueryThreatsArgs>;
threat?: Resolver<Maybe<Threat>, {}, Context, QueryThreatArgs>;
};
Threat?: {
id?: Resolver<Scalars['ID'], Threat, Context>;
name?: Resolver<Scalars['String'], Threat, Context>;
severity?: Resolver<Severity, Threat, Context>;
};
};
// Type-safe resolver implementation
export const resolvers: Resolvers = {
Query: {
threats: (_, args, context) => {
// args.severity is typed as Severity | undefined
const threats = context.db.threats.findAll();
if (args.severity) {
return threats.filter(t => t.severity === args.severity);
}
return threats;
},
},
};
Custom Scalar Mapping
# codegen.yml â custom scalars
schema: './src/schema/**/*.graphql'
generates:
src/generated/types.ts:
plugins:
- typescript
config:
scalars:
DateTime: Date
JSON: Record<string, unknown>
UUID: string
EmailAddress: string
Common Mistakes
1. Not Regenerating After Schema Changes
After changing the schema, run codegen. Stale types cause TypeScript compilation errors. Use --watch in development.
2. Ignoring Nullable Types in Generated Code
Generated types mark nullable fields with Maybe<T>. Forgetting to check for null before rendering causes runtime errors.
3. Not Configuring Custom Scalar Mappings
Without scalars.DateTime: Date, codegen types DateTime as any. Map custom scalars to their TypeScript equivalents.
4. Storing Generated Files in .gitignore
Generated type files should be committed to version control so CI doesn't need codegen to compile TypeScript.
5. Overgenerating Unnecessary Plugins
Each plugin adds build time. Only include plugins you actually use â typescript, typescript-operations, and typescript-react-apollo cover 90% of use cases.
Practice Questions
- What does graphql-codegen generate?
- What plugins are needed for React Apollo hooks?
- How do you map custom scalars to TypeScript types?
- Why should generated files be committed to git?
- How do you generate resolver types for the server?
Answers:
- TypeScript types for schema types, operation arguments, operation results, and React hooks. It reads SDL and
.graphqloperation files. typescript(base types),typescript-operations(operation types),typescript-react-apollo(React hooks with useQuery/useMutation).- In
config.scalars, map GraphQL scalars to TS types:DateTime: Date,JSON: Record<string, unknown>,UUID: string. - Committing generated files ensures other developers and CI can compile TypeScript without running codegen first.
- Use
typescript-resolversplugin withcontextTypeconfig pointing to your context type. GeneratedResolverstype ensures type-safe resolver implementations.
Challenge: Set up graphql-codegen for DodaTech's full schema. Generate types for 5+ schema types with enums, custom scalars (DateTime, JSON, UUID), 10+ operations, and React Apollo hooks. Add resolver types for the server with proper context typing. Configure custom scalar mappings and ensure all generated code compiles without errors.
FAQ
Mini Project
Set up graphql-codegen for DodaTech's GraphQL API. Configure TypeScript types for the full schema (Users, Devices, Threats, Scans, Alerts). Generate React Apollo hooks for all CRUD operations. Set up resolver types for the server. Add a npm script for codegen and a pre-commit hook to regenerate on schema changes.
What's Next
| Topic | Description |
|---|---|
| Testing | Testing GraphQL APIs |
| Apollo Server | Production server configuration |
| Performance | Query optimization and Caching |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro