Type Safety 
Type safety catches errors before your code runs, turning runtime bugs into compile-time errors.
The Problem with Runtime Errors 
Without type safety, mistakes only surface when code executes:
const { data } = await fetchUser(userId);
console.log(data.user.nmae);This code compiles successfully but crashes at runtime. The typo nmae instead of name goes unnoticed until a user triggers this code path.
With GraphQL, the problem compounds. Your schema defines what fields exist, but without type generation, nothing ensures your queries match:
const query = `
  query {
    user(id: "123") {
      name
      emial
    }
  }
`;The GraphQL server rejects this query because emial doesn't exist. But you don't discover this until the request fails in production.
Compile-Time Validation 
Type safety moves these errors to compile time. Your editor shows errors immediately:
const { data } = useQuery(
  graphql(`
    query GetUserQuery($id: ID!) {
      user(id: $id) {
        name
        email
      }
    }
  `),
  { id: userId },
);
console.log(data.user.nmae);TypeScript underlines nmae in red. The error appears in your editor before you save, preventing the bug entirely.
Full-Stack Type Flow 
Type safety works across your entire stack:
Schema → Operations 
Your GraphQL schema defines available types and fields. Build tools parse the schema and validate your operations against it. Queries requesting non-existent fields fail at build time:
graphql(`
  query {
    user(id: "123") {
      invalidField
    }
  }
`);Error: Field "invalidField" does not exist on type "User"
Operations → Variables 
Type generation creates interfaces for variables. Pass the wrong type and TypeScript catches it:
const GetUserQuery = graphql(`
  query GetUserQuery($id: ID!) {
    user(id: $id) {
      name
    }
  }
`);
useQuery(GetUserQuery, { id: 123 });Error: Type 'number' is not assignable to type 'string'
Variables → Components 
Generated types flow through to your components. Access non-existent properties and the compiler stops you:
const { data } = useQuery(
  graphql(`
    query GetUserQuery($id: ID!) {
      user(id: $id) {
        name
        email
      }
    }
  `),
  { id: userId },
);
return <div>{data.user.phoneNumber}</div>;Error: Property 'phoneNumber' does not exist on type 'User'
Developer Experience Benefits 
Type safety provides immediate feedback while coding:
Autocomplete 
Your editor suggests available fields as you type. No need to reference documentation or remember field names:
data.user.Autocomplete shows: id, name, email, avatar, bio, createdAt
Refactoring 
Rename a field in your schema and TypeScript shows every usage that needs updating. No grep, no missed references:
- Rename emailtoemailAddressin schema
- Build fails, showing all locations using email
- Update each location with confidence
- Build succeeds, nothing broken
Documentation 
Hover over any field to see its type and documentation from your schema:
data.user.createdAtTooltip shows: createdAt: DateTime - The date this user was created
Null Safety 
GraphQL's null semantics transfer to TypeScript. Non-nullable fields in your schema become non-optional properties:
type User {
  id: ID!
  name: String!
  bio: String
}Generated TypeScript:
interface User {
  id: string;
  name: string;
  bio: string | null;
}Access bio without checking and TypeScript warns you:
const length = data.user.bio.length;Error: Object is possibly 'null'
How Type Generation Works 
Type generation happens automatically at build time:
- Schema Loading - Build tool reads your GraphQL schema
- Operation Extraction - Parser finds all graphql()calls in your code
- Validation - Each operation is validated against the schema
- Type Generation - TypeScript types are generated for each operation
- File Output - Types are written to a generated file
This happens during development as you save files. No separate generation step required.
Zero Runtime Overhead 
Type generation produces only compile-time types. Generated code contains no type information:
const GetUserQuery = graphql(`
  query GetUserQuery($id: ID!) {
    user(id: $id) {
      name
    }
  }
`);Compiles to:
const GetUserQuery = {
  query: 'query GetUserQuery($id: ID!) { user(id: $id) { name } }',
  operationName: 'GetUserQuery',
};No types in the bundle. All type safety at compile time, zero cost at runtime.
Beyond Basic Types 
Advanced type features handle complex scenarios:
Conditional Fields 
Fields selected conditionally are typed as optional:
graphql(`
  query GetUserQuery($id: ID!, $includeBio: Boolean!) {
    user(id: $id) {
      name
      bio @include(if: $includeBio)
    }
  }
`);Generated type:
interface GetUserQueryData {
  user: {
    name: string;
    bio?: string | null;
  };
}Unions and Interfaces 
GraphQL unions and interfaces map to TypeScript discriminated unions:
interface Node {
  id: ID!
}
type User implements Node {
  id: ID!
  name: String!
}
type Post implements Node {
  id: ID!
  title: String!
}Generated types use __typename for discrimination:
type Node =
  | { __typename: 'User'; id: string; name: string }
  | { __typename: 'Post'; id: string; title: string };TypeScript narrows types based on __typename:
if (node.__typename === 'User') {
  console.log(node.name);
}Fragment Types 
Fragments generate reusable type fragments:
const UserCard_user = graphql(`
  fragment UserCard_user on User {
    name
    avatar
  }
`);Component props use generated fragment reference types:
interface UserCardProps {
  user: UserCard_user$key;
}This ensures the fragment is spread correctly in parent queries.
Type Safety vs Runtime Safety 
Type safety catches errors at compile time but doesn't validate runtime data. If your server returns unexpected data, TypeScript can't help:
const data = { user: { name: 123 } };This satisfies TypeScript if typed as { user: { name: string } } but crashes at runtime when you try name.toUpperCase().
GraphQL provides runtime schema validation server-side. Your server rejects malformed responses before they reach the client. This combines with compile-time types for comprehensive safety.
Next Steps 
- Modern GraphQL - Why GraphQL clients exist
- Caching - How data stays consistent across your app
- Fragments - How fragments enable component-level types
- Your First Query - See type safety in action