π GraphQL vs REST
Imagine ordering a burger and being handed the entire kitchen β the fries you didn't ask for, plus a drink you have to fetch from a second counter. That's the everyday friction of REST for data-hungry apps. GraphQL rethinks the deal: you write the order, the server assembles exactly that, and it all arrives in one bag.
Week 14 · Wednesday: GraphQL · Lecture 1
π― Learning Objectives
By the end of this lesson, you will be able to:
- Explain what GraphQL is β a query language plus a runtime β and how its single endpoint differs from REST's many endpoints
- Diagnose over-fetching and under-fetching in a REST API and describe how a client-shaped query eliminates both
- Read a GraphQL schema written in SDL and identify types, fields, non-null
!, and lists - Weigh the real trade-offs: REST's simplicity and HTTP caching versus GraphQL's flexibility and its caching/complexity costs
- Choose the right paradigm β or a hybrid β for a given project
Estimated Time: 55 minutes
Practice: Rewrite a chain of REST calls as a single GraphQL query and sketch its schema.
In This Lesson
What Problem Does GraphQL Solve?
APIs are the glue of modern software: they let a mobile app, a web front end, and a partner's server all talk to the same backend. For two decades, REST has been the default way to design those APIs, and for good reason β it's simple, it rides on plain HTTP, and it caches beautifully.
But as front ends grew richer β a single profile screen that needs a user, their recent posts, and their followers β REST started to strain. You either make three round trips, or you build a bespoke "give me everything for the profile screen" endpoint that no other screen can reuse. GraphQL, created at Facebook in 2012 and open-sourced in 2015, was the answer to exactly this pain: let the client describe the data it needs, and return precisely that β no more, no less, in one request.
This lesson is a fair comparison, not a sales pitch. By the end you'll know when GraphQL earns its keep and when REST is still the smarter call.
REST in One Minute
REST (Representational State Transfer) models your data as resources, each with its own URL, manipulated with the standard HTTP verbs. A blog's API might look like this:
GET /users # list users
POST /users # create a user
GET /users/123 # read user 123
PUT /users/123 # replace user 123
DELETE /users/123 # delete user 123
GET /users/123/posts # list that user's posts
The shape of each response is fixed by the server. Ask for /users/123 and you get whatever fields the endpoint decided to include β every time, for every client. That predictability is a strength for caching and a weakness for flexibility, as we'll see.
Notice the pattern: one screen, three round trips, three fixed payloads. Hold that picture β GraphQL is about to collapse it into one.
What GraphQL Is
GraphQL is two things at once:
- A query language β a small, typed syntax the client uses to describe the data and shape it wants.
- A runtime β server-side machinery that validates a query against a schema and resolves each requested field.
Here's a query that gathers, in a single request, exactly what a profile screen needs:
query {
user(id: "123") {
name
email
posts(limit: 3) {
title
commentCount
}
followers(first: 5) {
name
avatarUrl
}
}
}
The response mirrors the query's shape one-for-one β the client never has to guess where a field will land:
Response
{
"data": {
"user": {
"name": "Ada Lovelace",
"email": "ada@example.com",
"posts": [
{ "title": "On the Analytical Engine", "commentCount": 12 }
],
"followers": [
{ "name": "Charles B.", "avatarUrl": "/img/cb.png" }
]
}
}
}
π‘ The core idea
In REST the server decides the response shape; in GraphQL the client does. GraphQL flips control of the response to the consumer, which is why it shines when many different clients (web, iOS, Android) each want a different slice of the same data.
One Endpoint, Client-Shaped Queries
Perhaps the most visible difference: a GraphQL API exposes a single endpoint β conventionally POST /graphql β through which every operation flows. There is no /users, no /posts/123/comments; there is just the graph, and your query walks it.
Because the query itself carries the shape, adding a new screen never means adding a new endpoint β you just write a new query against the fields that already exist.
Over-fetching & Under-fetching
These two words are the heart of the GraphQL pitch. Learn them and you'll understand 80% of why teams adopt it.
Over-fetching β too much data
A REST endpoint returns a fixed payload. If GET /users/123 returns fifteen fields but your list view needs only name and avatarUrl, the other thirteen fields travelled the network for nothing. On a phone with a weak signal, that waste is real.
# GraphQL: ask for exactly two fields, get exactly two fields
query {
user(id: "123") {
name
avatarUrl
}
}
Under-fetching β too few endpoints
The opposite problem: one endpoint can't give you everything, so you make follow-up requests. Fetch the post, then fetch its author, then fetch its comments β the classic "waterfall" of round trips that slows a page down. GraphQL resolves related data in the same query, so one request replaces the chain.
π The made-to-order analogy
REST is a diner with a printed menu: you order dish #7 and it arrives exactly as the chef plates it β pickles you didn't want included (over-fetching), and if you also want soup you place a second order (under-fetching). GraphQL is a made-to-order counter: you list the ingredients you want, the kitchen assembles that single custom plate, and there's a full catalogue of every ingredient available (the schema).
The Strongly-Typed Schema
None of this magic works without a contract. Every GraphQL API is defined by a schema written in SDL (Schema Definition Language). The schema lists the types, their fields, and the entry points β and the runtime validates every incoming query against it before a single resolver runs.
type User {
id: ID! # ID! β non-null (the "!" means required)
name: String!
email: String # nullable β no "!", so it may be absent
posts: [Post!] # a list of non-null Posts
followers: [User!]
}
type Post {
id: ID!
title: String!
author: User! # relationships are just typed fields
comments: [Comment!]
}
# Entry points: every query starts from a field on Query
type Query {
user(id: ID!): User
users(limit: Int): [User!]!
post(id: ID!): Post
}
# Writes live under Mutation
type Mutation {
createUser(name: String!, email: String!): User!
deleteUser(id: ID!): Boolean!
}
| SDL piece | Meaning |
|---|---|
String, Int, Boolean, Float, ID | The five built-in scalar types |
! | Non-null β the field is guaranteed to have a value |
[Post!] | A list; the inner ! means no null items |
type Query | The root read entry points |
type Mutation | The root write entry points |
β The schema is self-documenting
Because the types are machine-readable, tools can introspect the schema and generate live documentation, autocomplete, and type definitions. There's no separate OpenAPI file to keep in sync β the schema is the documentation, and it can never drift out of date.
Trade-offs: Caching & Complexity
GraphQL is not a free upgrade. Its flexibility has a bill, and honest engineers read it before adopting.
Caching gets harder
REST leans on decades of mature HTTP caching: a GET /users/123 has a stable URL, so browsers, CDNs, and proxies can cache it with Cache-Control and ETag headers for free. GraphQL sends everything as a POST to one URL, so that URL-based layer doesn't apply. You cache at a different level instead β usually a normalized client cache (Apollo Client, urql) or persisted queries β which is more work to set up.
The N+1 problem
A query for 50 posts, each asking for its author, can naΓ―vely fire 1 query for the posts plus 50 queries for the authors β the dreaded N+1. It's solvable with batching tools like DataLoader, but it's a footgun REST rarely hands you.
Query cost is unbounded
Because clients compose their own queries, a malicious or careless one can nest deeply and ask the server to do enormous work. Production GraphQL needs depth limiting and query cost analysis β guardrails REST doesn't require.
| Dimension | REST | GraphQL |
|---|---|---|
| Endpoints | Many, resource-based | One, /graphql |
| Response shape | Fixed by server | Chosen by client |
| Over/under-fetching | Common | Eliminated by design |
| HTTP caching | Excellent, built-in | Needs custom strategy |
| Type system | External (OpenAPI, optional) | Built-in, mandatory |
| Versioning | /v1, /v2 | Evolve; deprecate fields |
| Learning curve / ops | Lower | Higher (N+1, cost limits) |
When to Use Which
The right answer is "it depends," and here's what it depends on.
Reach for REST when
- Your data maps cleanly onto resources and simple CRUD
- HTTP caching or a CDN is central to your performance story
- You're shipping a public API where a low learning curve drives adoption
- You handle file uploads and downloads (simpler over plain HTTP)
Reach for GraphQL when
- Many different clients each need a different slice of the same data
- You're aggregating several backends or microservices behind one graph
- Bandwidth is precious (mobile) and precise fetching matters
- Front-end requirements change fast and you want to add fields without new endpoints
π‘ It's not either/or
Plenty of production systems run both: GraphQL as a flexible aggregation layer for app screens, REST for file endpoints and simple public routes. GitHub, Shopify, and Netflix all expose GraphQL alongside REST. Choosing GraphQL is rarely a decision to delete REST.
Practice & Quiz
ποΈ Exercise 1: Collapse the waterfall
Goal: A dashboard currently makes three REST calls to render one card: the post, its author, and its comment count. Write the single GraphQL query that fetches all of it at once, requesting only title and createdAt from the post, name from the author, and the commentCount.
# The REST calls you're replacing:
GET /posts/42
GET /authors/<post.authorId>
GET /posts/42/comments # only need the count
π‘ Hint
Start from a root field β post(id: "42") β then nest the fields you need. Related data like the author is just a nested selection inside the post.
β Solution
query {
post(id: "42") {
title
createdAt
author {
name
}
commentCount
}
}
One request, exactly four pieces of data, no over-fetching. The three REST round trips become one.
ποΈ Exercise 2: Sketch the schema
Goal: Write the SDL for a minimal Book API: a book has a required id, a required title, an optional year, and a required author (a String). Add a Query with books (a non-null list of non-null books) and book(id: ID!).
β Solution
type Book {
id: ID!
title: String!
year: Int
author: String!
}
type Query {
books: [Book!]!
book(id: ID!): Book
}
Note that book(id: ID!) returns a nullable Book β a lookup by id can legitimately find nothing, so it must be allowed to return null.
π― Quick Quiz
Question 1: How many HTTP endpoints does a typical GraphQL API expose?
Question 2: Returning fields the client never asked for is calledβ¦
Question 3: Which is a genuine advantage of REST over GraphQL?
Best Practices & Pitfalls
β Do
- Pick the paradigm from the client's needs, not hype β flexibility for varied clients favours GraphQL, cacheable resources favour REST
- Design your GraphQL schema for how the API will be consumed, not around your database tables
- Plan for the N+1 problem from day one (we'll fix it with DataLoader in a later lesson)
- Keep REST for file transfer and simple public routes even in a GraphQL shop
β Don't
- Assume GraphQL is "REST but better" β you're trading free HTTP caching for flexibility
- Expose a GraphQL API in production without depth limiting and query cost controls
- Add a
/v2to a GraphQL API β evolve the schema and@deprecatedold fields instead - Mirror your REST endpoints one-to-one as GraphQL fields; think in terms of the graph of relationships
β οΈ Every field is a resolver
In GraphQL, each field can trigger its own data fetch. A query that looks cheap β users { posts { comments { author { name } } } } β can explode into thousands of database hits. Always ask "what does the server do to resolve this?" before assuming a query is light.
Summary
π Key Takeaways
- GraphQL is a query language plus a runtime: the client describes the data, the server resolves exactly that
- One single endpoint replaces REST's many resource URLs, and queries are client-shaped
- It eliminates over-fetching (too many fields) and under-fetching (too many round trips)
- A strongly-typed schema in SDL β types, fields, non-null
!, lists,Query/Mutationβ is the mandatory contract - The trade-off is real: REST is simpler and HTTP-cacheable; GraphQL is flexible but adds caching, N+1, and query-cost concerns
π Additional Resources
- graphql.org β Introduction to GraphQL
- graphql.org β Schemas and Types
- Apollo GraphQL Documentation
- Apollo β GraphQL vs REST
π What's Next?
Theory is settled β now let's build one. In the next lesson, Apollo Server setup, you'll stand up a real GraphQL server in Node with type definitions and resolvers, and fire your first query at it from the built-in Apollo Sandbox.
π Well compared!
You can now explain, to a skeptical teammate, exactly what GraphQL buys you and what it costs. That judgment is worth more than any single framework.