Free & open source — no account required
Free & open source — no account required
This technical guide provides an in-depth analysis of the graphql to typescript engine, best practices for implementation, and data security standards.
GraphQL's type system guarantees what the server can return. But that guarantee is meaningless unless your TypeScript client enforces it — and TypeScript interfaces written by hand drift from the actual schema within weeks. GraphQL Code Generator solves this by analyzing your schema and each individual operation (query, mutation, subscription) and generating TypeScript types that reflect exactly what you asked for, not the entire type. If your query selects username but not email, the generated type doesn't have email — accessing it is a compile error.
// GraphQL Schema (server-side, schema.graphql)
type User {
id: ID!
username: String!
email: String
role: Role!
posts: [Post!]!
createdAt: String!
}
type Post {
id: ID!
title: String!
status: PostStatus!
author: User!
}
enum Role { ADMIN EDITOR VIEWER }
enum PostStatus { DRAFT PUBLISHED ARCHIVED }
type Query {
user(id: ID!): User
posts(authorId: ID, status: PostStatus): [Post!]!
}
// Client query (queries/getUser.graphql)
query GetUser($id: ID!) {
user(id: $id) {
id
username
role
posts {
id
title
status
}
}
}
// Generated TypeScript (auto-generated, do not edit)
export type Role = 'ADMIN' | 'EDITOR' | 'VIEWER';
export type PostStatus = 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';
export type GetUserQueryVariables = {
id: string;
};
export type GetUserQuery = {
user?: {
id: string;
username: string;
role: Role;
posts: Array<{
id: string;
title: string;
status: PostStatus;
}>;
} | null;
};
Note that email is absent from GetUserQuery — the generator only includes fields the query actually selects. This is operation-specific typing: each query gets its own type, not a shared User interface that may have fields you didn't fetch.
// Install
npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations
// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: 'http://localhost:4000/graphql', // or './schema.graphql'
documents: ['src/**/*.graphql', 'src/**/*.tsx', 'src/**/*.ts'],
generates: {
'src/gql/types.ts': {
plugins: [
'typescript', // schema types
'typescript-operations', // query/mutation/subscription types
],
config: {
strictScalars: true,
scalars: {
ID: 'string',
Date: 'string',
DateTime: 'string',
JSON: 'unknown',
},
enumsAsTypes: true, // 'ADMIN' | 'EDITOR' instead of enum
avoidOptionals: { field: true }, // non-nullable fields are not optional
}
}
}
};
export default config;
# Run once
npx graphql-codegen
# Watch mode during development
npx graphql-codegen --watch
# Validate queries against schema (CI check)
npx graphql-codegen --require dotenv/config --config codegen.ts
import { useQuery, useMutation } from '@apollo/client';
import { gql } from '@apollo/client';
import type { GetUserQuery, GetUserQueryVariables } from '../gql/types';
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
username
role
posts { id title status }
}
}
`;
// data is typed as GetUserQuery
function UserProfile({ userId }: { userId: string }) {
const { data, loading, error } = useQuery<GetUserQuery, GetUserQueryVariables>(
GET_USER,
{ variables: { id: userId } }
);
if (!data?.user) return null;
// data.user.email — TypeScript error: property 'email' does not exist
// data.user.username — string ✓
// data.user.role — 'ADMIN' | 'EDITOR' | 'VIEWER' ✓
// data.user.posts[0].status — 'DRAFT' | 'PUBLISHED' | 'ARCHIVED' ✓
return <h1>{data.user.username}</h1>;
}
// Define reusable fragments
fragment UserPreview on User {
id
username
role
}
fragment PostCard on Post {
id
title
status
author { ...UserPreview }
}
// Use in multiple queries
query GetPosts($authorId: ID) {
posts(authorId: $authorId) {
...PostCard
}
}
// Code generator creates fragment types:
// type UserPreviewFragment = { id: string; username: string; role: Role }
// type PostCardFragment = { id: string; title: string; status: PostStatus; author: UserPreviewFragment }
// Import fragment types for component props
import type { PostCardFragment } from '../gql/types';
function PostCard({ post }: { post: PostCardFragment }) {
return <div>{post.title} — {post.author.username}</div>;
}
// codegen.ts — add typescript-resolvers plugin for backend
generates: {
'src/gql/resolvers.ts': {
plugins: ['typescript', 'typescript-resolvers'],
config: {
contextType: '../context#AppContext', // your resolver context type
mappers: {
User: '../models/user#UserModel', // map schema User to your ORM type
Post: '../models/post#PostModel',
},
}
}
}
// Generated resolver types ensure implementation matches schema
import type { Resolvers } from '../gql/resolvers';
import type { AppContext } from '../context';
export const resolvers: Resolvers = {
Query: {
user: async (_, { id }, ctx: AppContext) => {
return ctx.db.users.findById(id);
// return type: UserModel | null (matches schema User | null)
},
posts: async (_, { authorId, status }, ctx) => {
return ctx.db.posts.findMany({ authorId, status });
}
},
User: {
posts: async (parent, _, ctx) => {
// parent is UserModel — typed, not any
return ctx.db.posts.findByAuthor(parent.id);
}
}
};
// schema.graphql
scalar Date
scalar JSON
scalar PositiveInt
// codegen.ts — map scalars to TypeScript types
config: {
scalars: {
Date: 'string', // ISO 8601 date string
JSON: 'unknown', // untyped JSON
PositiveInt: 'number', // validated at runtime
Upload: 'File', // file upload scalar
}
}
// Custom scalar resolvers (server-side)
import { GraphQLScalarType } from 'graphql';
const DateScalar = new GraphQLScalarType({
name: 'Date',
serialize: (value: Date) => value.toISOString().split('T')[0],
parseValue: (value: string) => new Date(value),
parseLiteral: (ast) => new Date((ast as any).value),
});
strictScalars: true: Without this, unknown scalars default to any. With it, unregistered scalars cause a codegen error, forcing you to explicitly map every scalar to a TypeScript type..graphql files next to the components that use them. Code Generator picks up queries from documents: 'src/**/*.graphql'. Co-location makes it obvious what data each component needs.enumsAsTypes: TypeScript string unions ('ADMIN' | 'EDITOR') are more ergonomic than TypeScript enums — they don't require imports and interoperate naturally with string comparisons.Q: Does GraphQL Code Generator work with any GraphQL server?
A: Yes — it works with any server exposing a standard GraphQL endpoint or schema file. Supports introspection from a URL, local SDL files, and JSON introspection results. Works with Apollo Server, Yoga, Hasura, AWS AppSync, and any SDL-compliant server.
Q: tRPC vs. GraphQL — when to choose each?
A: tRPC is ideal for full-stack TypeScript projects where you control both client and server — zero schema definition, types are inferred from your router functions. GraphQL is better for public APIs, mobile clients, multi-language backends, or cases where introspection and tooling (GraphiQL, Postman) matter.
Q: How do I handle authentication in GraphQL types?
A: Authentication is a runtime concern — your resolver context carries the auth token or user session. The GraphQL schema defines what fields exist; authorization is enforced in resolvers. Tools like graphql-shield let you define a permission schema that wraps your resolvers.
Q: Why are my nullable fields typed as T | null but I expected T | undefined?
A: GraphQL nullable fields are T | null — GraphQL has no concept of undefined. The schema's String (without !) means "may return null", which maps to string | null. Use maybeValue: T | null | undefined in your config if your client may receive undefined for missing fields.
Is the processing local-only?
Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.
Can I use this for enterprise projects?
Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.
Why pasting proprietary company data into third-party web tools is a major liability, and how to stay safe.
A deep dive into combining Zod, React Query, and TypeScript for bulletproof API integration.
Code generation is just the beginning. Discover how a schema-first approach can eliminate 90% of your integration bugs.