REST's field-selection lesson mentioned clients sometimes need less data, or differently-shaped data, than a fixed resource shape provides. GraphQL exists because that problem doesn't fully go away with a query parameter bolted on — it's a different way of thinking about the API contract entirely.
2 min read
Two related, common REST pain points: over-fetching (an endpoint
returns twenty fields when a screen needs three) and under-fetching
(rendering one screen requires calling /users/5, then /users/5/posts,
then /posts/12/comments — several round trips to assemble one view).
Sparse fieldsets (lesson 3) address over-fetching partially; under-fetching
generally needs a genuinely different approach.
Instead of a fixed set of endpoints each returning a fixed shape, GraphQL exposes one endpoint and lets the client send a query describing exactly which fields it wants, across however many related resources are needed, resolved by the server in a single round trip:
query {
user(id: 5) {
name
posts {
title
comments {
text
}
}
}
}{
"data": {
"user": {
"name": "Alex",
"posts": [
{ "title": "Hello world", "comments": [{ "text": "Nice post!" }] }
]
}
}
}One request, and the response shape exactly mirrors the query shape — no
unused fields, no separate follow-up calls for the related posts and
comments data.
Every GraphQL API defines a schema — a strongly-typed description of every object, field, and relationship the API exposes:
type User {
id: ID!
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
comments: [Comment!]!
}The ! marks a field as non-nullable. A client can only ever query fields
that actually exist in the schema — an unrecognized field name is rejected
before the query even runs, which is a meaningfully stronger contract than
a REST API's response shape, which is typically only documented, not
enforced by the transport itself.
Queries read data (the example above). Mutations are GraphQL's
explicit convention for anything that changes data — deliberately named and
structured differently from queries so it's unambiguous, just by looking at
the operation type, whether a given request is safe to retry or not (the
same underlying idempotency concern System Design's idempotency lesson
covers for REST's GET vs. POST):
mutation {
createPost(title: "New post", body: "...") {
id
title
}
}There's no equivalent of REST's HTTP status codes or methods — every
request is typically a POST to one endpoint, and a request that partially
succeeds still returns 200 OK with an errors array alongside whatever
data did resolve. This is a deliberate design choice reflecting that a
GraphQL query can touch many resources at once, some of which might fail
independently — REST's one-status-code-per-whole-response model doesn't map
cleanly onto that, so GraphQL trades it for a different error-reporting
shape (lesson 8 covers structured API error design more generally).
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What two REST pain points does GraphQL directly address?
2. What does a GraphQL schema's type definitions enforce that a typical REST API's documentation doesn't?
3. Why does GraphQL use a distinctly-named 'mutation' operation for writes instead of using 'query' for everything?
API Design