"REST is dead, everything should be GraphQL now." We've been hearing that for a decade, and meanwhile the vast majority of the APIs running the world — Stripe, GitHub, Twilio — are still REST... or they're GraphQL. The debate is framed wrong: they're not rivals, they're tools that solve different problems.
The right question isn't "which one is better?" but "what problem am I looking at?". This guide gives you the decision framework we use at HexagonalGuru when designing an API for a client: no hype, just the real costs of each option.
The Problem: Choosing by Fashion
We've audited projects running GraphQL on top of three CRUD tables (a cannon to kill a fly, N+1 included) and mobile apps hitting twelve REST endpoints to render a single screen. In both cases the team paid a price: unnecessary complexity or poor performance. Choosing by fashion instead of by problem is technical debt from the very first commit.
REST in Essence
REST models the API as resources (/users/42, /orders) manipulated with HTTP verbs. Its strengths have been proven for twenty years:
- Simplicity and predictability. Any developer understands
GET /orders/42without documentation. - Free HTTP caching. CDNs, proxies and browsers cache GET responses with standard headers. It's the cheapest way to scale reads that exists.
- Mature ecosystem. OpenAPI, client generators, testing tools, per-endpoint monitoring — it's all been invented already.
- Semantic status codes. A 404 is a 404. A 429 is a 429. Infrastructure (load balancers, WAFs, dashboards) understands them.
GraphQL in Essence
GraphQL exposes a single endpoint and a typed schema; the client asks for exactly the fields it needs in one query:
Its real strengths:
- No more overfetching and underfetching. The client gets what it asks for — not one field more, not one request less. On mobile, over bad networks, you feel it.
- A schema as a living contract. Introspection, autocompletion and query validation against the schema. Documentation can't drift because it is the schema.
- Independent frontends. Web, iOS and Android request different shapes of the same data without asking the backend for changes. It shines as a BFF (Backend for Frontend) layer.
What Nobody Tells You About GraphQL
GraphQL isn't free. These are the costs we see over and over in our audits:
- The resolver N+1. One innocent nested query can fire hundreds of database queries. Without dataloaders (batching), your GraphQL is a performance-problem generator.
- Caching gets complicated. Everything is a
POSTto the same endpoint: HTTP caching stops working. You get to reinvent it (persisted queries over GET, field-level cache, TTLs in the schema). - Rate limiting by complexity. Counting requests is no longer enough: a single query can cost a thousand times more than another. You need depth and query-cost limits.
- Errors with 200 OK. Error responses arrive with HTTP 200 and an
errorsarray inside. Your dashboards and alerts need adapting. - Learning curve and tooling. Schema, resolvers, dataloaders, fragments, client-side code generation... it's another layer of knowledge the team must master.
The Same Example in Both Worlds
A profile screen showing the user's orders. In REST, the client orchestrates:
GET /users/42
GET /users/42/orders?limit=5
GET /products/101
Each response carries all its fields (overfetching) and the client chains requests (underfetching). In GraphQL, a single query declares the exact shape:
query {
user(id: 42) {
name
orders(limit: 5) {
id
total
lines { product { name } quantity }
}
}
}
One request, zero spare fields. The trade-off? The backend must resolve that query without turning it into an N+1 festival:
// Resolver with dataloader: one query per batch, not one per order
public function lines(array $orderIds): array
{
$lines = $this->lines->findByOrders($orderIds); // 1 query
return array_map(fn ($id) => $lines[$id] ?? [], $orderIds);
}
How to Decide: The Practical Framework
Choose REST when:
- Your API is a reasonably direct CRUD or serves stable, predictable resources.
- HTTP caching and the CDN are part of your performance strategy (public content, catalogs).
- Your consumers are third parties or server-to-server integrations: REST with OpenAPI is the common denominator everyone knows how to consume.
- The team is small and doesn't need another technology to maintain.
Choose GraphQL when:
- You have several clients (web, mobile, partners) needing very different data shapes over the same domain.
- Your screens aggregate data from many entities and overfetching is costing you real performance on mobile.
- It acts as an aggregation layer (BFF) over several internal services: the client sends one query, GraphQL orchestrates.
- You have a team capable of operating its particularities (dataloaders, persisted queries, complexity limits).
And the answer that most often turns out to be right in large systems: both. REST for integrations and cacheable resources, GraphQL as a BFF for the apps. They coexist just fine.
Common Mistakes
- GraphQL as a direct database proxy. Resolvers running one query per field, with no dataloaders. Performance sinks exactly when traffic peaks.
- Exposing GraphQL publicly without limits. With no depth or complexity limit, anyone can take your API down with a recursive query.
- Sloppy REST. Collections without pagination, responses without field filtering, zero versioning and errors that always return 500. REST wasn't the problem.
- GraphQL for a simple CRUD. If your API is four flat entities and a single client, you're paying the cost without collecting the benefit.
- Choosing before measuring. If you don't know how many requests your screens make today, you don't know whether you have an overfetching problem or a different one entirely.
The Bottom Line
REST and GraphQL don't compete: they solve different problems. REST wins on simplicity, caching and integrations; GraphQL wins when multiple clients need different data shapes and overfetching genuinely hurts. Decide based on your clients, your domain and your team's capacity — not on the trending article of the week.
Designing an API, or inherited one that doesn't perform as it should? In our audits we measure real behavior (requests per screen, N+1, caching) and hand you a concrete plan. Request a free consultation →
Keep reading: Web performance: a practical guide to speeding up your application.