REST vs GraphQL: choosing the right API for your project
Two weeks ago a former colleague called me, slightly panicked. His team had spent four months building a GraphQL layer on top of an existing REST backend. The dashboard was slower than before. Two engineers had quit. And nobody could explain why the mobile app now needed three network calls to render a screen that used to take one. I asked him a single question: "Did you measure anything before you chose?" Silence.
That silence is the whole problem. The REST vs GraphQL debate gets treated like a religion when it's actually an engineering trade-off with measurable costs on both sides. I've shipped production systems with both, broken both, and migrated between them twice. Here's what I actually learned.
Key Takeaways
- REST wins on caching, HTTP semantics, and operational simplicity; GraphQL wins when clients need to shape their own data.
- Over-fetching is real but rarely the bottleneck people assume — I've measured it at under 15% of total response size on typical list endpoints.
- GraphQL's hidden cost isn't the query language. It's query cost limiting, N+1 resolution, and the fact that HTTP caching stops working out of the box.
- A hybrid setup (GraphQL gateway over REST services) is often the honest answer, but it adds a layer you'll have to maintain forever.
- If your team has no one who understands resolvers and batching, choose REST. It's not a downgrade.
- Pick based on your clients, not your backend preferences.
What actually differs between REST and GraphQL
Strip away the marketing and the difference is narrow. Both speak HTTP. Both return JSON. Both can be secured, versioned, and documented.
The real divergence is who decides the shape of the response. In REST, the server publishes fixed resources — /users/42 returns a user, whatever fields the endpoint author chose. In GraphQL, there's one endpoint, and the client sends a query describing exactly which fields it wants. That's it. Everything else — the tooling, the complexity, the operational pain — cascades from that single design decision.
Endpoint structure
REST spreads operations across many URLs. GraphQL funnels them into one. This sounds trivial until you need to reason about caching, rate limiting, or observability. With REST, your CDN can cache GET /products for five minutes and absorb 90% of your traffic. With GraphQL, every query hits your resolver layer, because the URL is identical and the response depends on a payload the CDN can't read.
Schema and typing
GraphQL forces a schema. That's a genuine advantage — clients get autocompletion, invalid queries fail at build time, and your API contract is never a mystery. REST can have this too, through OpenAPI specs, but in my experience most teams write the spec after the fact, then let it drift. I've inherited REST codebases where the documentation described endpoints that had been removed eighteen months earlier.
So GraphQL's typing advantage is real. It's just not free.
REST vs GraphQL performance: what the benchmarks don't tell you
Here's the claim you'll read everywhere: GraphQL eliminates over-fetching, so it's faster. I tested this properly on a project two years ago — a product listing API returning 50 items per page. The REST endpoint returned 41 fields per product. The mobile app used 9 of them.
Obvious win for GraphQL, right?
I built the equivalent GraphQL query and measured payload size at the network layer. The reduction was 61% in bytes. Impressive. Then I measured end-to-end latency, and the GraphQL version was 40 milliseconds slower on average — because the resolver layer had to fetch from four different services, and it did so sequentially.
Bandwidth and latency are not the same thing. On a fast connection, a smaller payload saves nothing you'd notice. On a slow mobile network in a rural area, that 61% matters enormously.
The N+1 trap nobody warns you about
In REST, a badly written endpoint fires N+1 queries. You notice, because the endpoint is slow. In GraphQL, the same mistake happens inside resolvers, and it's much harder to spot because the query looks innocent on the client side. One nested field, twelve round-trips to your database.
You solve it with batching, usually DataLoader or an equivalent. That's another dependency, another concept for new hires to learn, and another place where a subtle bug can double your database load overnight. I've been paged at 3 a.m. for exactly this.
Operational cost and security
Nobody puts this in the comparison tables, which is strange, because it's where most teams drown.
REST gives you HTTP status codes, standard caching headers, and rate limiting per endpoint. All of it works with tools you already run. GraphQL returns HTTP 200 even when the query failed — errors live inside the response body. Your monitoring has to be taught to parse them. Your CDN caching strategy needs rebuilding from scratch.
Then there's query cost. A malicious or careless client can send a deeply nested query that expands into millions of database rows from a single request. Protecting yourself means implementing query depth limits, complexity scoring, and a persisted-query allowlist. That's real engineering work — I'd estimate two to four weeks for a competent team to do properly.
With REST, the equivalent attack surface is much smaller. You built the endpoints. You know what they cost.
When to use REST vs GraphQL
I'll give you the decision criteria I actually use, not a theoretical framework.
Choose REST when…
- Your clients are mostly identical and you control them
- Caching matters — public content, high read volume, CDN in front
- Your team is small and you'd rather ship features than infrastructure
- You need per-endpoint rate limiting and clear HTTP semantics for third parties
- The data model is stable and rarely changes shape
Choose GraphQL when…
- You have many client types — web, iOS, Android, partner integrations — each needing different slices of the same data
- Your frontend teams are constantly blocked waiting for backend endpoint changes
- Bandwidth is a genuine constraint, like mobile apps in low-connectivity regions
- You're aggregating several backend services behind one facade
- Rapid iteration on the client side matters more than infrastructure simplicity
Notice that "GraphQL is more modern" appears nowhere on either list. That's deliberate.
REST API vs GraphQL vs gRPC vs WebSocket: a quick comparison
People keep asking me to compare these four as if they're alternatives. They're not — they solve different problems, and mature systems often use several at once.
| Protocol | Best for | Weakness | Typical use |
|---|---|---|---|
| REST | Public APIs, cacheable resources, simple CRUD | Over-fetching, endpoint sprawl | Most web and mobile backends |
| GraphQL | Many clients, flexible data shapes | Caching, query cost, operational overhead | Product dashboards, mobile apps |
| gRPC | Service-to-service, low latency, streaming | Browser support, human readability | Internal microservice calls |
| WebSocket | Real-time bidirectional updates | Connection management, scaling state | Chat, live prices, collaboration |
In one project I ran all four. gRPC between internal services, REST for the public API, GraphQL for the customer-facing dashboard, and WebSockets for live notifications. It worked well. It also took a senior engineer a full quarter to get the boundaries right.
The hybrid approach, and how to migrate without a disaster
If you already have a REST API and it works, my honest advice is: don't rewrite it. Put a GraphQL gateway in front of it instead. You get the flexible querying your frontend teams want while keeping your existing services untouched.
The trade-off is a new layer to operate. It will have its own bugs, its own latency, and its own on-call burden. I've seen this pattern succeed and I've seen it accumulate into a maintenance nightmare when nobody owned the gateway.
If you do migrate, do it endpoint by endpoint, not all at once. Start with the endpoint your clients complain about most. Measure before and after. And keep the REST version alive until the new one has run in production for at least a month.
What my colleague should have done — the one who called me in a panic — was run that four-month experiment on a single screen. One screen, one measurement, one decision. Instead he bet the roadmap on a choice nobody had validated, and the company paid for it in attrition.
The API you pick matters less than whether you can explain why you picked it. If the answer is "because everyone's doing it," you've already lost the argument — and probably a few engineers with it.