Free & open source — no account required

v1.2.5-PRICING-19
Web & Frontend • Engineering Documentation

GraphQL to TypeScript Generator

This technical guide provides an in-depth analysis of the graphql to typescript engine, best practices for implementation, and data security standards.

GraphQL to TypeScript: Operation-Specific Types with GraphQL Code Generator

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.

Live Example: Schema, Query, and Generated Types

// 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.

Setting Up GraphQL Code Generator

// 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

Using Generated Types with Apollo Client

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>;
}

Fragment-Based Type Reuse

// 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>;
}

Server-Side: Generating Resolver Types

// 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);
    }
  }
};

Custom Scalar Types

// 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),
});

Best Practices for Production

  • Commit generated files to the repository: Generated types should be checked in, not gitignored. CI should regenerate and check for diffs — a diff means a query or schema changed without the types being regenerated.
  • Use 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.
  • Co-locate queries with components: Keep .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.
  • Use enumsAsTypes: TypeScript string unions ('ADMIN' | 'EDITOR') are more ergonomic than TypeScript enums — they don't require imports and interoperate naturally with string comparisons.

FAQ

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.

Developer FAQ

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.