Everyone compares the query languages. The structural difference is caching, and the operational difference is four pieces of security work that REST never asks for. Neither belongs in a footnote.
REST versus GraphQL turns into architecture tribalism faster than almost any other technical argument, which is unhelpful when you are actually deciding what to build next week. Both are mature. Both are well supported. The right one depends on your data shape, your client types and your team, and the comparison that usually gets published stops at the query language, which is the least consequential part of the difference.
The part everyone agrees on
REST exposes fixed, resource-based endpoints and leans on standard HTTP semantics. GraphQL exposes a single endpoint where the client specifies exactly the shape of data it needs in one request, which avoids both the over-fetching and the under-fetching REST produces when one screen needs data from several resources at once.
That is real and it is genuinely valuable. A dashboard pulling from five resources is the canonical case, and anyone who has hand-rolled six parallel fetches and a merge function knows exactly what problem is being solved.
The structural difference is caching, and it is not a matter of effort
Here is the bit worth internalising before you commit, because it is architectural rather than something you can out-work.
HTTP caching keys on the request method and the URL. That means REST gets shared caches, CDN edges and conditional requests essentially for free — not as a feature somebody implemented, but as a consequence of using the protocol the way it was designed.
GraphQL sends most operations as POSTs to a single endpoint. Those layers cannot cache that. Not "cache it badly" — cannot participate at all, because there is nothing to key on. Equivalent caching therefore has to be rebuilt inside the application.
The GraphQL project documents this openly rather than hiding it, and points at persisted queries and per-field caches as the answer. Those work. They are also work, and they are work that lands on your team rather than on infrastructure you were already paying for.
What GraphQL costs to run safely
This is the part that tends to be discovered rather than estimated.
A REST endpoint has a roughly knowable worst case. It does one thing, and you can rate-limit it by URL. A GraphQL endpoint accepts queries nobody on your team wrote, so its worst case is whatever a stranger can compose. OWASP's guidance on this is blunt about the fact that the controls are not built in, and it names four pieces of work REST never asks for.
Depth limiting, which GraphQL has no native support for, so you add it yourself.
Query cost analysis: assigning a cost to resolving each field so an expensive query can be refused before it runs rather than after it has finished taking down a database.
Defences against batching and aliasing abuse, where many object requests collapse into a single call that can enumerate users, emails and IDs while looking to your rate limiter and your WAF like one entirely innocent request. This is the one I would put first if I could only fix one, because it is the one where the monitoring actively lies to you.
And turning introspection off in production, which OWASP recommends system-wide for anything publicly reachable.
None of these is hard on its own. Together they are a body of work that belongs in the estimate rather than in a retrospective.
The authenticated-endpoint objection
The common response is that the API sits behind a login, so most of this does not apply.
Batching abuse is the clearest reason it does. Because many requests collapse into one call, an authenticated account can enumerate objects at a rate your rate limiter never sees, since as far as it is concerned one request arrived. A login narrows who can try it. It does not change what the endpoint will answer once they do.
Introspection is worth the same caution in the other direction. Disabling it raises effort rather than closing the door — field names can still be guessed, and tooling exists specifically to guess them. Treat it as one layer, not as the defence.
"GraphQL is faster" is a claim about round-trips, not about speed
The performance argument gets made in both directions and it is imprecise in both.
GraphQL reduces round-trips. That is the actual claim and it is true. On a slow network, collapsing six requests into one is a genuine and sometimes dramatic improvement, and it is the reason mobile clients benefit most.
What it does not do is make anything faster on the server. It shifts complexity there. One query touching five resources still resolves five resources, and now it does so through a resolver graph that can accidentally produce an N+1 pattern per field rather than per endpoint. Dataloader-style batching exists precisely because that failure mode is the default rather than the exception, and it is a thing you have to know about in advance rather than discover.
So the honest version is: fewer round-trips, more server-side work to do correctly, and a performance profile that is harder to reason about because the query shape is no longer under your control. On a fast network with a well-cached REST API, GraphQL can easily be the slower option end to end.
Neither framing survives contact with measurement, which is the usual conclusion for performance arguments conducted at the level of architecture diagrams.
The versioning difference, which is quieter than it looks
REST versions by URL, which everyone complains about and which has one large advantage: the old thing keeps working, visibly, until you turn it off, and you can see who is still calling it.
GraphQL's answer is to evolve the schema — add fields, deprecate the ones you are removing, never break the contract. That is genuinely nicer when it works, and it moves the problem rather than eliminating it. The deprecated field stays in the schema, sometimes for years, because you cannot prove nobody is requesting it without per-field observability that you have to build. The schema accretes.
Both approaches end in the same place: something is still serving a shape you regret. REST makes it obvious and ugly. GraphQL makes it invisible and tidy. Knowing which failure mode your team will actually act on is a reasonable input to the decision, and it is not the input anyone uses.
Where REST still wins, plainly
Simplicity, broad tooling support and aggressive cacheability at the HTTP and CDN layer make REST the better default for straightforward CRUD, for public APIs with predictable consumers, and for teams who want the lowest possible learning curve on the thing that is least interesting about their product.
That last one is underrated. "Our API is boring and every developer who joins already knows how it works" is a real operational property with real value, and it does not show up in any feature comparison.
Where GraphQL earns its complexity
Complex nested data from multiple resources in one view. Several very different client types — a mobile app and a web app with genuinely different data needs — served from one backend. Slow networks where round-trips are expensive enough that collapsing them changes the experience. A front-end team that needs to change what it queries without a backend deploy for every change.
Those are real wins and they are worth paying for when you have them. The mistake is paying for them when you do not.
The decision, compressed
Simple CRUD, a public API, or a small team new to API design: REST. Complex nested queries, multiple frontends with different shapes, or a fast-moving frontend team that needs query flexibility without backend changes: GraphQL earns its overhead.
And a lot of products end up mixing both, deliberately — REST for simple resources, a GraphQL layer specifically where aggregation across sources is the actual problem. That is not fence-sitting. It is scoping the complexity to the part of the product that needs it.
Whichever way you go, price the caching strategy and the four security controls into the decision rather than into the first incident. That is the entire argument.
Read the full version
This is the condensed version. The full article covers the decision framework in more detail, how mixed architectures tend to get shaped in practice, and the questions clients ask before committing to one.
REST vs GraphQL: Choosing the Right API Architecture
This copy is a snapshot from the day it was posted, and it will not be updated here. The original on our site is the version we keep current: when a study publishes new figures, a price changes or a tool is renamed, that is where the correction lands. If you want the up-to-date version, follow the link above.
Sources
The single-endpoint model and the project's own framing of caching and query complexity as areas needing deliberate design are in Learn GraphQL, with the normative behaviour in the GraphQL specification. How shared caches key on method and URL is documented by MDN, and the method semantics REST leans on are in RFC 9110. Depth limiting, query cost analysis, batching and aliasing abuse and the introspection recommendation are all from the OWASP GraphQL Cheat Sheet. The decision framework is mine.
If you run GraphQL in production, I would like to know which of the four controls you actually implemented and which one you found out about the hard way, because I suspect the answer is not evenly distributed.


