Part 1: Introduction to GraphQL and SDL

How I Came to GraphQL

For years I built REST APIs and assumed the problem was just clients being needy. The mobile app wants a stripped-down user summary? Make a new endpoint. The dashboard needs data from three services? Make three sequential requests. I treated every extra endpoint as a product requirement rather than an API design problem.

The pivot happened while working on a platform that served both a React frontend and a mobile app. The REST API I had designed was correct by REST standards but neither client could use it efficiently. The web frontend was making six requests per page load; the mobile app was downloading 40 fields when it used three. I added projection parameters, created view-specific endpoints, and the API became harder to maintain with every sprint.

A colleague suggested GraphQL. I dismissed it at first โ€” "it's just REST with extra steps." It is not. Once I understood that GraphQL moves the query responsibility to the client and the type system makes that safe, I rebuilt the API in GraphQL and the mobile team stopped asking for new endpoints entirely.

This part covers what GraphQL is, how it differs from REST, and how to read and write Schema Definition Language (SDL). By the end you will have a running Apollo Server with TypeScript.

What is GraphQL?

GraphQL is three things:

  1. A query language for APIs โ€” clients describe what data they need in a structured syntax

  2. A type system โ€” the server defines all available data and operations in a schema written in SDL

  3. A runtime โ€” the server validates every incoming query against the schema and executes only what was asked

The GraphQL specificationarrow-up-right was open-sourced by Facebook in 2015. It is not tied to any database, transport, or language. Any server that implements the spec can be called a GraphQL server.

Core Concepts

The Schema is the Contract

In GraphQL, the schema is the single source of truth. It defines every type, every field, and every operation available. Unlike REST where the contract lives in documentation (OpenAPI, README files, or institutional memory), the GraphQL schema is the contract and is machine-readable.

Clients Ask for Exactly What They Need

The defining behaviour of GraphQL: clients specify the exact fields they want, the server returns precisely those fields.

REST returns the full resource shape:

GraphQL returns only what was requested:

Single Endpoint

All GraphQL operations go to a single endpoint โ€” typically POST /graphql. The operation type (query, mutation, subscription) and the requested fields are in the request body, not the URL. This is a significant mental shift from REST.

The Schema Definition Language (SDL)

SDL is the language used to define a GraphQL schema. It is human-readable, language-agnostic, and becomes the specification for your API. Every tool in the GraphQL ecosystem reads SDL.

Scalar Types

GraphQL has five built-in scalar types:

Scalar
Description

String

UTF-8 string

Int

32-bit signed integer

Float

Double-precision floating point

Boolean

true or false

ID

Unique identifier โ€” serialised as a string

Custom scalars are covered in Part 2.

Non-Null and Lists

By default, every field in GraphQL is nullable. The ! suffix marks a field as non-null โ€” the server guarantees it will never return null for that field.

Reading list nullability:

  • [Role!]! โ€” the list itself is non-null AND every item in the list is non-null

  • [String!] โ€” the list may be null, but if present, items are non-null

  • [String] โ€” both the list and its items may be null

Object Types

Input Types

Input types are the only way to pass complex objects as arguments to mutations. Output types (e.g. Product) cannot be reused as inputs.

Note that UpdateProductInput uses nullable fields โ€” only the fields provided will be updated.

Queries, Mutations, and Subscriptions

These three operation types map to the Query, Mutation, and Subscription root types:

Arguments and Default Values

Fields can have arguments with optional default values:

GraphQL vs REST vs gRPC

From personal experience, here is when I reach for each:

Dimension
GraphQL
REST
gRPC

Primary use

Frontend-facing APIs, flexible queries

Standard CRUD APIs, public APIs

Internal microservice communication

Request shape

Client-defined (query language)

Server-defined (fixed response)

Strict schema (proto)

Over-fetching

Eliminated by design

Common

Eliminated by design

Under-fetching

Eliminated by design

Common

Eliminated by design

Real-time

Subscriptions (WebSocket)

SSE / polling workaround

Native streaming

Browser support

Excellent (HTTP POST)

Excellent

Needs proxy (gRPC-web)

Tooling maturity

Excellent

Excellent

Good

Learning curve

Medium

Low

Medium-High

Inter-service calls

Adds overhead vs gRPC

Common, well-understood

Best performance

When I choose GraphQL over REST: frontend-facing APIs where multiple clients (web, mobile, public API) need different field subsets, or where the number of "combination" endpoints is growing uncontrollably.

When I stick with REST: simple CRUD with a single client, public webhooks, file upload/download, or when response caching at the CDN level is critical.

When I use gRPC instead: service-to-service internal communication where performance and strict contracts matter more than query flexibility.

Setting Up Apollo Server 4 with TypeScript

Let me walk through the complete setup I use as a starting point.

Project Initialisation

package.json

tsconfig.json

Your First Schema and Server

Running Your First Query

Open http://localhost:4000 in a browser โ€” Apollo Server serves a built-in sandbox where you can write and run queries interactively.

Or with curl:

Response:

How GraphQL Executes a Query

Understanding execution helps debug unexpected resolver behaviour.

When the server receives:

Execution proceeds field-by-field:

  1. GraphQL validates the query against the schema (is product a valid query field? does it accept an id argument? are name and price valid fields on Product?)

  2. The Query.product resolver runs with args = { id: "1" }

  3. Its return value ({ id: "1", name: "Widget Pro", price: 29.99, inStock: true }) becomes the parent for the next level

  4. The Product.name resolver runs โ€” but since there is no custom resolver for name, GraphQL uses the default resolver: parent.name

  5. Same for Product.price

  6. inStock is in the parent object but was not requested โ€” it is omitted from the response

The default resolver (parent[fieldName]) handles most scalar fields automatically. You only need to write custom resolvers when:

  • The field name in GraphQL differs from the field name in the data source

  • The field requires a database query (related entities)

  • The field applies business logic before returning

What's Next

You now understand:

  • What GraphQL is and why it exists

  • The SDL building blocks: scalars, types, inputs, enums, non-null, lists

  • Queries, mutations, and subscriptions

  • How GraphQL executes a query

  • A running Apollo Server 4 with TypeScript

In Part 2, we go deeper into the type system โ€” interfaces, unions, custom scalars, and how to design a schema that models a real domain and evolves without versioning.

Last updated