"Should we use REST or GraphQL?" is the wrong question framed too generally. The right question is: given who calls this API and how often, which set of trade-offs do we accept?
Side by Side
| Concern | REST | GraphQL |
|---|---|---|
| Endpoints | Many resource URLs | One endpoint |
| Over-fetching | Common; mitigations require effort | Solved by design |
| Multiple round trips | Normal | One query gets graph |
| Caching | Native HTTP caching, CDN-friendly | Hard; needs persisted queries or app-layer cache |
| Tooling | Universal | Strong but ecosystem-specific |
| Learning curve | Low | Moderate to high |
| Schema | OpenAPI optional | Mandatory and central |
| Real-time | SSE, WebSockets bolted on | Subscriptions native |
| Operational complexity | Lower | Higher (depth limits, query cost, auth-per-field) |
Where REST Wins
- Public APIs. Universal client support, easy curl, predictable URLs.
- Cache-heavy reads. CDN and HTTP caches just work.
- Simple CRUD. When the API mostly mirrors database tables, REST is shorter to write and operate.
- Webhook-style integrations. Inbound POSTs are inherently REST-ish.
- Long-lived APIs with stable shapes. Less benefit from GraphQL's flexibility.
Where GraphQL Wins
- Multiple client surfaces. Mobile + web + smart-TV with different field needs.
- Aggregation across services. One query stitches data from several backends; GraphQL gateway becomes the BFF (Backend For Frontend).
- Rich relational data. Deep graphs that REST would force into many round trips.
- Rapid product iteration. Adding fields is cheap; clients consume them at will.
- Strict type contracts. Generated client types prevent whole categories of bug.
The Caching Story
HTTP caching for REST is a superpower: ETags, Cache-Control, CDNs, browser cache, 304 Not Modified. GraphQL gives this up by routing all traffic through one POST endpoint with a body.
Mitigations:
- Persisted queries — clients send a hash; the server has the query; can route over GET and cache.
- Automatic Persisted Queries (APQ) — Apollo's optimisation that bootstraps the hash on first use.
- Application-layer caches — Redis around resolvers; not as cheap as HTTP cache.
If your API is read-mostly and globally distributed, REST + CDN is hard to beat.
The Security Story
REST authorises per endpoint. GraphQL must authorise per field — because one query can touch many resources. Done well, this is fine. Done poorly, it is the number-one source of GraphQL data leaks.
Other GraphQL-specific concerns:
- Query depth limits — reject queries deeper than N.
- Query complexity limits — assign costs to fields; reject expensive queries.
- Disable introspection in production for sensitive APIs.
- Persisted queries to prevent arbitrary queries from clients.
Hybrid Patterns
- REST outside, GraphQL inside. Public REST API for partners; GraphQL gateway for internal product clients aggregating microservices.
- GraphQL outside, REST inside. Mobile-first product with a GraphQL BFF; REST microservices behind it.
- REST + GraphQL on the same gateway. Stripe, GitHub, Shopify all expose both for different audiences.
You don't have to choose globally; you choose per surface.
Decision Framework
- Who calls this API — partners, your own product, third-party developers?
- How varied are the client needs — fixed and CRUD-shaped, or wide and aggregating?
- How read-heavy is it — does CDN caching matter?
- How real-time — does subscription pull its weight?
- What does the team know — REST is universal; GraphQL needs experience.
Default to REST unless GraphQL clearly wins on at least two of those axes.
Anti-Patterns
- Using GraphQL because it is fashionable and then implementing every query as
POST /api/{operationName}. - Calling a REST API "GraphQL" because it accepts a JSON filter body.
- Exposing every database column as both — doubles your maintenance, halves your testing.
Cert Mapping
| Cert | Where this shows up |
|---|---|
| AWS SAA | API Gateway (REST/HTTP) vs AppSync (GraphQL) |
| AWS Data Engineer | Selecting the right access pattern for data products |
Whatever you pick, the next lesson applies: every API, regardless of style, needs authentication and authorisation.