Skip to content
API Design Learn/GraphQL Foundations
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

Schema-First Design

7 min read

You'll learn to

  • -Write GraphQL types, scalars, and the non-null (!) modifier correctly
  • -Explain why the schema is the actual contract in GraphQL, replacing the role many separate REST endpoint definitions play

A REST API's contract is spread across every endpoint's individual request/response shape. GraphQL inverts this: there is exactly one schema, describing every type and every possible query/mutation the API supports, and every client request is validated against that single schema before it ever executes.

Types and Scalars

A basic GraphQL type, built from scalars and other types
type User {
  id: ID!
  name: String!
  email: String!
  age: Int
  isVerified: Boolean!
  orders: [Order!]!
}

type Order {
  id: ID!
  total: Float!
  placedBy: User!
}
  • -GraphQL's built-in scalars: `Int`, `Float`, `String`, `Boolean`, `ID` (a unique identifier, serialized as a string but semantically distinct from `String`).
  • -A custom type (`User`, `Order`) is a named object composed of fields, each with their own type.
  • -A field's type can be another object type, letting the schema express relationships (`orders: [Order!]!`) the same way a foreign key expresses a relationship in a database schema.

The Non-Null Modifier: A Deliberate, Consequential Choice

A `!` after a type means the field can never be `null` - the server guarantees it will always return a value if the query includes that field at all. This sounds like a minor detail, but getting it wrong in either direction has real cost: marking a field non-null that later turns out to need to be nullable is a breaking schema change (existing clients assumed it could never be null and may not handle a sudden `null`), while marking everything nullable "to be safe" pushes null-checking onto every single client for data that, in practice, is always present.

The practical difference non-null makes to callers
type User {
  name: String!   # every User always has a name - clients never null-check this
  bio: String     # bio is genuinely optional - clients must handle null

The Query Root

The entry points every query starts from
type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  order(id: ID!): Order
}

The `Query` type is the schema's entry point - every field on it is a top-level query a client can run, each with its own arguments (like `id: ID!` for a single-user lookup). Everything reachable from a `Query` field's return type (here, all of `User`'s fields, and transitively `Order`'s fields through `orders`) becomes queryable in a single request, which is the structural source of GraphQL's "ask for exactly what you need, in one round trip" property.

Treat non-null as a promise you have to keep for the schema's entire lifetime, not a default to reach for reflexively - when genuinely unsure whether a field could ever legitimately be absent, nullable is the safer starting point, since loosening a non-null field later is a breaking change but tightening a nullable one rarely is.

Interview Signal is part of Pro

See a real weak answer next to a real strong one for this exact topic.

Quiz is part of Pro

Test what you just read with a short quiz, and bank the XP.

Ready to Build This?

Design Schema Genesis in the API Design Lab's GraphQL Mastery act.

ScaleDojo Logo
Initializing ScaleDojo