The GraphQL Type System & Schema

GraphQL — The GraphQL Type System & Schema
GraphQL
Chapter 2 · The GraphQL Type System & Schema

📐 The GraphQL Type System & Schema

Chapter 1 established why GraphQL exists. Everything else in this course builds on one artifact: the schema — written in the Schema Definition Language (SDL) — which is the explicit, machine-readable contract every GraphQL API is built around.

Scalar Types

GraphQL ships with five built-in scalar types:

ScalarMeaning
IntA signed 32-bit integer
FloatA signed double-precision floating-point value
StringUTF-8 text
Booleantrue or false
IDSerialized as a string, but semantically an identifier — not meant to be treated as human-readable text

Object Types

type User { id: ID! name: String! email: String age: Int }

The ! means non-nullid and name are guaranteed to always have a value; email and age may be null.

Lists: Where Nullability Gets Confusing

The ! can apply to the list itself, the items inside it, both, or neither — each combination means something different:

SyntaxMeaning
[Post]The list itself might be null; if present, individual items might also be null
[Post!]The list itself might be null; but if present, every item is guaranteed non-null
[Post]!The list is guaranteed to exist (even if empty); but individual items inside it might be null
[Post!]!The list is guaranteed to exist, and every item in it is guaranteed non-null — the strictest, and most common, form

🚪 Query and Mutation: The Entry Points

Two special types define every operation an API supports — nothing is queryable or writable unless it's listed here:

A Small Complete Schema

type Query { user(id: ID!): User posts: [Post!]! } type Mutation { createPost(title: String!, authorId: ID!): Post! } type User { id: ID! name: String! posts: [Post!]! } type Post { id: ID! title: String! author: User! }

Query — Reading Only

Every possible top-level read operation is listed as a field on type Query — Chapter 1's user(id: 42) { name } query is calling the user field defined here.

Mutation — Writing Data

Write operations get their own root type. Chapter 4 covers mutations and input types in depth — for now, note that they're just another set of fields, defined the same way as Query's.

Enums

enum PostStatus { DRAFT PUBLISHED ARCHIVED }

📝 Schema-First Design

The schema is normally written before the resolver functions that implement it — a contract-first approach, similar in spirit to the schema-first validation pattern from the TypeScript Real World Applications course. This lets frontend and backend teams work against an agreed shape in parallel, and lets tools generate documentation, type checking, and even mock servers directly from the schema itself.

Implicit Contract vs Explicit Schema

REST

The "contract" is whatever the JSON response happens to contain — discoverable only through documentation (if it exists and is current) or trial and error.

GraphQL

The schema is the contract — machine-readable, and queryable by clients themselves via introspection, which is what powers tools like GraphiQL's autocomplete.

💻 Coding Challenges

Challenge 1: Write a Schema for a Comment System

Write SDL for a Comment type (with id, text, and an author field pointing to a User) and add a comments: [Comment!]! field to the Post type from this chapter's example schema.

Goal: Practice writing object type definitions and connecting them to existing types.

→ Solution

Challenge 2: Explain the Four List Nullability Forms

For each of [Post], [Post!], [Post]!, and [Post!]!, write one sentence describing what a client's code would need to defensively check for that the other three forms wouldn't require.

Goal: Practice reasoning about nullability from the client's perspective, not just the schema's.

→ Solution

Challenge 3: Add an Enum to the Schema

Add a status: PostStatus! field to the Post type, using the PostStatus enum from this chapter, and explain why an enum is a better fit here than a plain String.

Goal: Practice choosing the right type for a field with a fixed set of valid values.

→ Solution

⚠️ Gotcha: Getting List Nullability Backwards

It's easy to write [Post]! when [Post!]! was actually intended, or vice versa — the ! placement is subtle and the two forms look nearly identical. The practical difference is real: with [Post]!, client code technically has to check every individual item in the list for null before using it, even though the list itself is guaranteed to exist; with [Post!]!, only an empty array is possible, never a null entry inside it. Getting this wrong either forces defensive null-checks that should never have been necessary, or — worse — lets a client assume every item is safe to use when the schema never actually guaranteed that.

🎯 What's Next

With the schema's shape defined, the next chapter covers how it actually gets filled with real data: Queries & Resolvers — the resolver function signature, and how a query maps down through nested fields.