GraphQL Code-First vs Schema-First

There are two main ways to build a GraphQL schema: write the SDL (Schema Definition Language) first and generate code from it, or write code first and generate the SDL from it. Both approaches produce the same working API. The difference is where you spend your time and what you maintain as the source of truth.

Schema-First Approach

You write the SDL in a .graphql file. This SDL is the source of truth. Tools generate TypeScript types from it. Resolvers are written separately and must match the schema manually.

  Workflow:
  ──────────
  1. Write SDL in schema.graphql
  2. Run code generator → TypeScript types
  3. Write resolvers that match the schema

  schema.graphql:
  ────────────────
  type Book {
    id:     ID!
    title:  String!
    author: String!
  }

  type Query {
    books: [Book!]!
  }

  index.ts (resolvers):
  ─────────────────────
  const resolvers = {
    Query: {
      books: (): Book[] => db.books.findAll()
    }
  };

Code-First Approach

You write classes or objects in TypeScript. The framework generates the SDL automatically. TypeNexus and Pothos are popular code-first frameworks for Node.js.

  Workflow:
  ──────────
  1. Write type definitions in TypeScript
  2. Framework generates SDL automatically
  3. No separate .graphql files to maintain

  Using Pothos (code-first):
  ───────────────────────────
  import SchemaBuilder from '@pothos/core';
  const builder = new SchemaBuilder({});

  const BookType = builder.objectType('Book', {
    fields: (t) => ({
      id:     t.exposeID('id'),
      title:  t.exposeString('title'),
      author: t.exposeString('author'),
    })
  });

  builder.queryType({
    fields: (t) => ({
      books: t.field({
        type: [BookType],
        resolve: () => db.books.findAll(),
      })
    })
  });

  // Generated SDL (automatic):
  // type Book { id: ID! title: String! author: String! }
  // type Query { books: [Book!]! }

Side-by-Side Comparison

  Aspect              Schema-First         Code-First
  ──────              ────────────         ──────────
  Source of truth     .graphql SDL file    TypeScript code
  Type safety         Via code generator   Built-in from types
  SDL file            Written manually     Generated automatically
  Learning curve      Familiar SDL syntax  Framework-specific API
  Refactoring         Update SDL + types   Update code only
  Team collaboration  Non-devs can read    Devs only
  Large schema        Can become unwieldy  Code scales better
  Best for            API design-first     Developer-first teams

When to Choose Schema-First

Schema-first works well when API designers, product managers, or frontend developers collaborate on the schema before any backend code is written. The SDL is human-readable and easy to review in pull requests. It also fits teams using GraphQL Code Generator heavily.

When to Choose Code-First

Code-first works well when the backend team builds the schema iteratively alongside the business logic. Because TypeScript types and GraphQL types live in the same file, there is no risk of them drifting out of sync. Pothos and TypeGraphQL are popular choices.

Hybrid Approach

Many large projects start schema-first for API design reviews, then move to code-first as the implementation grows. The generated SDL from code-first tools can be committed to the repository so API consumers still have a readable schema file to reference.

Key Points

  • Schema-first uses SDL as the source of truth; code-first uses TypeScript as the source of truth.
  • Schema-first is more collaborative and readable for non-developers; code-first offers stronger type safety with less duplication.
  • Both produce an identical, working GraphQL schema at runtime.
  • Code generators like GraphQL Code Generator bridge the gap for schema-first teams needing TypeScript types.

Leave a Comment