GraphQL
REST hands the client a fixed shape: you call an endpoint, you get whatever that endpoint decided to return. GraphQL flips the authority. It lets the client describe, in a query, the exact shape of the data it wants — which fields, how deep, across which related objects — and the server returns precisely that, no more and no less. It is a single endpoint and a query language, born at Facebook to feed mobile apps that were drowning in chatty, mismatched REST calls.
The problem it attacks: over- and under-fetching
Section titled “The problem it attacks: over- and under-fetching”Two failure modes haunt fixed-shape REST endpoints:
OVER-FETCHING UNDER-FETCHING (the "N+1 round trips" of REST)GET /users/42 returns 30 fields; GET /users/42 → the userthe screen needs 3. You shipped 27 GET /users/42/posts → their postsuseless fields over a mobile network. GET /posts/9/comments → comments on each post 3+ sequential round trips to paint one screen.A GraphQL query asks for the whole tree at once, exactly fitted to the screen:
query { user(id: 42) { name posts(last: 3) { title comments { author } } }}One request, one round trip, one response shaped like the query. For a phone on a flaky connection rendering a complex screen, this is a real, felt improvement. That is the value proposition.
How it works: the schema and resolvers
Section titled “How it works: the schema and resolvers”GraphQL has two halves. First, a schema — a strongly typed description of every type and field the API offers. Like a protobuf contract in gRPC, the schema is a single source of truth, and clients get autocompletion and validation against it.
Second, resolvers — a function attached to each field that knows how to fetch that field’s value. When a query arrives, the server walks the query tree and calls the resolver for each requested field.
query field resolver called───────────── ───────────────user(id: 42) → userResolver(42) → fetch user row posts → postsResolver(user) → fetch that user's posts comments → commentsResolver(post)→ fetch each post's commentsThis per-field resolution is the source of both GraphQL’s flexibility and its most infamous problem.
The N+1 problem
Section titled “The N+1 problem”Look again at that resolver tree. Suppose user has 3 posts. The posts resolver returns 3 posts,
and then GraphQL calls the comments resolver once per post — 3 separate calls. Scale that up: a
query over 100 posts triggers 1 query for the posts, then 100 queries for their comments. That is
the N+1 problem: one query to get the list, then N more to enrich each item.
1 query → fetch 100 posts100 queries → fetch comments for post 1, post 2, ... post 100= 101 database round trips for what should be ~2.It is not unique to GraphQL — any per-item lazy fetch can cause it — but GraphQL’s field-by-field resolver model makes it the default outcome unless you actively prevent it.
What it costs versus REST
Section titled “What it costs versus REST”GraphQL’s power is real, and so is its bill. What does this buy us, and what does it cost?
- Caching gets hard. REST gets HTTP caching almost for free: a
GET /users/42is a stable URL that browsers, CDNs, and proxies happily cache. GraphQL is typically onePOSTendpoint whose meaning lives in the request body — generic HTTP caches can’t see inside it. You must build caching at the application or client layer instead. You traded effortless network-level caching for query flexibility. - Query complexity becomes an attack surface. Because the client controls the shape, a malicious or careless client can request a deeply nested, expensive query that hammers your database. You must add depth limits, complexity scoring, and rate limits — guardrails REST’s fixed shapes gave you implicitly.
- Server complexity rises. Resolvers, batching, schema stitching, and authorization-per-field are genuine engineering work. REST’s mental model is simpler.
| Dimension | REST | GraphQL |
|---|---|---|
| Fetch shape | server-defined, fixed | client-specified, flexible |
| Over/under-fetching | common | largely solved |
| HTTP/CDN caching | easy (stable URLs) | hard (single POST endpoint) |
| Cost control | implicit (fixed endpoints) | explicit (depth/complexity limits) |
| Typical home | public, cacheable, simple | rich clients, many varied views |
When to reach for it
Section titled “When to reach for it”GraphQL pays off when many diverse clients (web, iOS, Android, partners) need different slices of an interconnected graph of data, and the cost of round trips or over-fetching is high. It pays off less for a simple, cacheable, mostly-read API where REST’s free HTTP caching and operational simplicity dominate. Like everything in this part, the right answer is “it depends on what you’re optimizing” — and now you know which knobs each choice turns.
The thread
Section titled “The thread”Who decides the shape of the data — server or client? REST says the server; GraphQL hands that authority to the client. That single shift eliminates over- and under-fetching and delights demanding front ends — but it pushes new costs onto you: the N+1 trap in resolvers, the loss of easy HTTP caching, and the need to police query complexity yourself.
Check your understanding
Section titled “Check your understanding”- Define over-fetching and under-fetching, and show how a single GraphQL query addresses both.
- What is a resolver, and why does the per-field resolver model make the N+1 problem the default rather than an edge case?
- How does a batching DataLoader turn N+1 queries back into roughly two?
- Why is HTTP/CDN caching so much harder for GraphQL than for REST?
- Name one situation where REST is the better choice than GraphQL despite GraphQL’s flexibility, and say why.