REST vs GraphQL: Choosing an API Style
REST exposes fixed endpoints per resource; GraphQL lets clients query exactly the fields they need through one endpoint. How to choose between them.
REST structures an API around fixed endpoints that each represent a resource, returning a predetermined shape of data for every request to that endpoint. GraphQL instead exposes a single endpoint backed by a schema, letting each client specify exactly which fields it wants in a query. Both are ways of designing the contract between a client and a server — the difference is who decides what shape a response takes: the server, in REST’s case, or the client, in GraphQL’s.
How REST works
A REST API organizes functionality around resources, each addressed by a URL, with the HTTP verb (GET, POST, PUT, DELETE) describing the action:
GET /users/42
GET /users/42/posts
POST /users/42/posts
DELETE /posts/107
Each endpoint returns a fixed shape — hitting /users/42 typically returns the full user object, whatever fields that includes, whether or not the client needs all of them. This is simple and predictable: the response for a given endpoint rarely surprises you, and REST maps naturally onto HTTP caching, since GET requests to the same URL can be cached by intermediaries like a CDN without any special handling.
How GraphQL works
GraphQL exposes one endpoint (commonly /graphql) and lets the client send a query describing exactly the fields it wants, across potentially multiple related resources, in a single request:
query {
user(id: 42) {
name
posts(limit: 3) {
title
publishedAt
}
}
}
The server resolves that query against a strongly typed schema and returns exactly the requested shape — no more, no less. A single request can pull data that would otherwise require several REST calls (the user, then separately their posts), which is GraphQL’s signature advantage: it collapses what REST calls “N+1 requests” into one round trip.
The core trade-off
The difference boils down to who controls the response shape.
With REST, the server decides. Every consumer of /users/42 gets the same fields, whether they’re a mobile app that only needs a name and avatar, or an admin dashboard that needs everything. This tends to produce either over-fetching (getting fields you don’t need) or under-fetching (needing a second request for related data), and API teams often respond by adding new endpoints or query parameters to cover each new client’s needs — which grows the surface area over time.
With GraphQL, the client decides. Each consumer asks for precisely what it needs, which eliminates over- and under-fetching by construction. The trade-off is that the server has to do more work per request — resolving an arbitrary combination of fields instead of returning one fixed payload — and that flexibility makes caching much harder, since two clients rarely send identical queries the way they’d hit identical REST URLs.
REST vs GraphQL
| REST | GraphQL | |
|---|---|---|
| Endpoints | Many, one per resource | Single endpoint |
| Response shape | Fixed by the server | Chosen by the client, per query |
| Over/under-fetching | Common | Eliminated by design |
| HTTP caching | Native, via URLs | Requires custom caching layers |
| Related data | Often needs multiple requests | Usually one request |
| Schema | Optional (often OpenAPI as a separate spec) | Built in, strongly typed, self-documenting |
| Learning curve | Lower, maps to familiar HTTP verbs | Higher, requires learning the query language |
| Tooling maturity | Extremely broad and mature | Strong but narrower |
Where each one wins
REST tends to be the better default for public APIs, simple CRUD services, and anything that benefits from HTTP’s native caching and the enormous ecosystem of tooling built around it — proxies, gateways, monitoring, and client libraries that all assume URL-per-resource semantics. It’s also just simpler to reason about for small APIs: there’s no query language to learn, and curl-ing an endpoint is enough to understand what it returns.
GraphQL tends to win in applications with many different client types (web, mobile, various dashboards) pulling overlapping-but-different slices of a large, deeply related data graph. It shines when a product team doesn’t want backend engineers to become a bottleneck for every new field a frontend needs — the frontend can query for the exact shape it wants without a backend change. It also earns its keep when reducing round trips genuinely matters, such as a mobile client on a slow connection that would otherwise need several sequential REST calls.
Not mutually exclusive
Plenty of real systems use both. A public-facing REST API might sit alongside an internal GraphQL layer used only by the product’s own frontends, or a GraphQL gateway might sit in front of several existing REST services, aggregating them into one queryable graph without requiring a rewrite of the underlying services. Choosing between them isn’t necessarily an all-or-nothing decision for an entire organization — it’s a decision you can make per API, based on who’s consuming it and how varied their data needs are.
It’s also worth knowing that neither is the only alternative: gRPC targets a different problem entirely — fast, strongly-typed service-to-service communication rather than flexible client-facing queries — and is common inside backend systems even when the public-facing API is REST or GraphQL.
The takeaway
REST organizes an API around fixed, server-defined endpoints, trading flexibility for simplicity and native HTTP caching. GraphQL organizes an API around a single, client-queried schema, trading server complexity and harder caching for precise, over-fetch-free responses. Reach for REST when you want simplicity, broad tooling, and cacheable resources; reach for GraphQL when multiple clients need different slices of deeply related data and round trips are expensive. Many systems end up using both, applied where each fits best.
Tagged
Keep reading
Chisato · · 4 min read gRPC vs REST: Choosing an API Style
gRPC uses binary Protocol Buffers over HTTP/2 for fast, typed service calls; REST uses JSON over HTTP for accessible, resource-based APIs. How to pick.
Takina · · 4 min read What Is the Backend-for-Frontend (BFF) Pattern?
A backend-for-frontend (BFF) is a dedicated backend layer for one client type — shaping, aggregating, and simplifying calls to shared downstream APIs.
The Lycoris Team · · 4 min read API Versioning Strategies Explained
URI paths, custom headers, and content negotiation are the three common ways to version an API. Tradeoffs of each, and how to avoid breaking clients.