API design comparison
REST vs GraphQL
Two different ways to design an HTTP API. Choosing between them is one of the most discussed architectural decisions of the last decade — and one of the most common topics in code reviews and system-design interviews.
TL;DR
- REST exposes resources at stable URLs (one URL per resource type). Clients call multiple endpoints to assemble what they need. Great for simple, cacheable, public APIs.
- GraphQL exposes a single endpoint with a typed schema. Clients write a query describing exactly the data they want. Great for product APIs powering many frontends with different data needs.
- Neither is universally better. The choice depends on your client diversity, caching needs, schema evolution discipline, and team experience.
Side-by-side comparison
| Aspect | REST | GraphQL |
|---|---|---|
| Shape | Multiple URL endpoints per resource | Single endpoint, query-driven |
| Data fetching | Fixed response per endpoint; over- or under-fetching common | Client picks exact fields → no over-fetching |
| Verbs / methods | HTTP verbs: GET, POST, PUT, PATCH, DELETE | Query, Mutation, Subscription (over POST or WebSocket) |
| Type system | Optional (OpenAPI / JSON Schema) | Built-in, mandatory schema (SDL) |
| Versioning | /v1/, /v2/ in URL or header | Schema evolution + @deprecated |
| Caching | HTTP caching out of the box (CDN-friendly) | Normalised client caches; harder on the wire |
| Error handling | HTTP status codes (4xx / 5xx) | Always 200 OK; errors in "errors" array |
| N+1 problem | Client orchestrates calls (front-end sees it) | Resolvers expose it on the server (DataLoader pattern) |
| Tooling | Mature: Swagger, Postman, curl | Strong: GraphiQL, Apollo Studio, codegen |
| Learning curve | Low — HTTP knowledge is enough | Higher — schema design, resolvers, federation |
| Best for | Public APIs, partner integrations, file uploads | Product APIs, mobile + web sharing one backend |
Code side-by-side
Fetching a user with their last 5 orders:
REST (often 2 calls)
GET /api/users/42
→ { "id": 42, "name": "Alice", "email": "..." }
GET /api/users/42/orders?limit=5
→ [
{ "id": 901, "total": 42.00, "createdAt": "..." },
{ "id": 887, "total": 19.50, "createdAt": "..." },
...
] GraphQL (one call)
POST /graphql
{
user(id: 42) {
name
email
orders(limit: 5) {
id
total
createdAt
}
}
} When to use REST
- Public API consumed by third parties. REST is the lingua franca — every language has an HTTP client; every developer knows GET/POST.
- You need HTTP caching. Static-ish resources cached at the CDN edge are dramatically cheaper than running every query through your servers.
- File uploads / downloads. GraphQL handles these awkwardly; multipart uploads via REST are simpler.
- Webhooks and event delivery. Outbound events to partner systems are almost always REST POST callbacks.
- Small team, no GraphQL experience. REST has lower ramp-up cost and fewer footguns.
When to use GraphQL
- Multiple clients with different data needs. Mobile, web, and partner apps can each ask for what they need without separate endpoints.
- Aggregating multiple backend resources. A GraphQL layer becomes a thin orchestration on top of microservices.
- Strong schema discipline desired. The mandatory schema becomes the contract between teams; codegen produces typed clients automatically.
- Rapid product iteration. Adding fields without releasing new endpoints reduces deploy coupling.
- Federated architecture. Multiple teams own subgraphs that compose into one supergraph (Apollo Federation, Hot Chocolate, etc.).
English phrases engineers use
REST conversations
- "We expose an endpoint at
/api/users/:id." - "This returns a 404 if the resource is missing."
- "The response is idempotent — same input, same output."
- "Let's version the API behind
/v2/." - "The endpoint is over-fetching — the mobile app only needs three of the twelve fields."
GraphQL conversations
- "We have a schema-first approach — the SDL is the source of truth."
- "The resolver fetches related orders from the orders service."
- "We're hitting N+1 on the comments resolver — let's add a DataLoader."
- "The field is deprecated with
@deprecated(reason: ...)— clients have 60 days to migrate." - "This is a breaking change on the schema — we'll need a coordinated rollout."
Quick decision tree
- Public API consumed by external partners → REST
- Need aggressive HTTP/CDN caching → REST
- One backend, many frontends with different data needs → GraphQL
- Mobile-first product where round trips hurt → GraphQL
- Many microservices, want a single client API → GraphQL (Federation)
- Team has no GraphQL experience and a tight deadline → REST
- Need file uploads → REST (or hybrid)
Frequently asked questions
What is the main difference between REST and GraphQL in plain English?
REST organises an API around resources (URLs like /users/42), and clients call separate endpoints to fetch related data. GraphQL exposes a single endpoint and lets clients describe exactly the shape of the data they want in one query — including nested resources.
When should I use REST instead of GraphQL?
Use REST when your API is simple and resource-shaped (CRUD on a few entities), when HTTP caching matters (REST plays well with CDNs), when consumers are external partners who expect a conventional REST API, or when your team has no in-house GraphQL experience.
When is GraphQL the better choice?
Pick GraphQL when clients (mobile, multiple frontends) need to compose data from many backend resources and want to avoid over-fetching; when you have a typed schema you want to share across teams; or when you need strong client-side tooling (auto-generated types, IDE autocomplete).
Is GraphQL faster than REST?
Not inherently. GraphQL can reduce the number of round trips (one query instead of N REST calls), which often feels faster on mobile networks. But a single GraphQL query can be more expensive on the server, especially with deeply nested resolvers. The right comparison is "total latency for the actual workload", not raw protocol overhead.
Do REST and GraphQL handle versioning differently?
REST typically versions through the URL (/v1/users, /v2/users) or an Accept header. GraphQL discourages versioning — instead, you evolve the schema by adding new fields and deprecating old ones with the @deprecated directive. Clients only break if they actively request a removed field.
Which is easier to cache?
REST is significantly easier to cache because each URL maps to a stable response, so HTTP caching, CDNs, and ETag/If-None-Match all work out of the box. GraphQL traditionally uses POST for queries, which most CDNs do not cache by default — you typically rely on client-side normalised caches (Apollo, urql, Relay) instead.
How do error responses compare?
REST uses HTTP status codes (4xx for client errors, 5xx for server errors) and a body schema. GraphQL always returns 200 OK and reports errors in a top-level "errors" array — the response can be partially successful (some fields resolved, others failed). This trips up engineers used to REST conventions.