REST vs GraphQL
Two different answers to the same problem — how a client asks a backend for exactly the data it needs.
2 min read
REST and GraphQL both solve the same core problem — letting a client get data from a backend over HTTP — but they structure the client-server contract in fundamentally different ways.
REST: fixed endpoints, fixed shapes
In REST, each endpoint returns a fixed data shape. If you need a user's name and their five most recent orders, you typically make two requests, or the backend has to build a special combined endpoint for that one screen:
GET /users/42 -> { id, name, email, ... }
GET /users/42/orders -> [{ id, total, status }, ...]
This leads to two common problems as an app's UI needs grow:
- Over-fetching — an endpoint returns fields the client doesn't need for this particular screen (the full user object, when the UI only shows a name).
- Under-fetching — the client needs data from multiple endpoints and has to make several round trips, or the backend team ends up building a bespoke endpoint per screen.
GraphQL: one endpoint, client-specified shape
GraphQL exposes a single endpoint and a typed schema; the client sends a query describing exactly the fields it wants, across relationships, in one request:
query {
user(id: 42) {
name
orders(limit: 5) {
id
total
status
}
}
}The server returns exactly that shape — no more, no less — in one round trip, regardless of how many underlying resources it had to pull together.
{
"data": {
"user": {
"name": "Ada",
"orders": [{ "id": 1024, "total": 49.99, "status": "shipped" }]
}
}
}Tradeoffs
| | REST | GraphQL |
|---|---|---|
| Learning curve | Low — just HTTP | Higher — schema, resolvers, a query language |
| Caching | Simple — HTTP caching works out of the box | Harder — a single POST /graphql endpoint doesn't cache the same way |
| Over/under-fetching | Common problem | Solved by design |
| Backend complexity | Simpler per-endpoint logic | Resolvers can hide expensive nested queries (its own N+1 risk) |
| Tooling maturity | Universal, decades old | Strong but younger, more setup |
| Versioning | Often via URL/header (/v2/...) | Typically evolves the schema instead, deprecating fields |
Which to reach for
REST remains the default for most APIs — public APIs, simple CRUD services, anything where HTTP caching and broad tooling support matter more than flexible querying. GraphQL earns its complexity in products with many different clients (web, iOS, Android) each needing different slices of the same data, or complex UIs assembled from many nested relationships, where the alternative would be dozens of bespoke REST endpoints or chatty multi-request screens.
Neither is "correct" — plenty of large-scale products run entirely on REST, and plenty run a GraphQL layer in front of REST or gRPC services. The two lessons that follow — API versioning and pagination — apply mainly to REST, since GraphQL handles both differently by design.