Ask five engineers whether you should use REST or GraphQL and you'll get five confident answers pointing in different directions — usually shaped by whatever they used most recently. Both are legitimate, mature choices. The useful question isn't "which is better," it's "which one matches the shape of my problem."
What each one actually optimizes for
REST organizes an API around resources — /users/42, /orders/17/items — and lets HTTP verbs and status codes carry most of the meaning. It leans on decades of tooling: caching proxies, browser dev tools, and infrastructure that already understands HTTP semantics out of the box.
GraphQL organizes an API around a single endpoint and a typed schema, and lets the client describe exactly which fields it needs in one request. Instead of the server deciding what a response contains, the client does.
That one difference — who decides the shape of the response — explains almost every tradeoff that follows.
Where REST tends to win
- Simple, resource-shaped domains. If your app is mostly CRUD over a handful of clear entities, REST's conventions map onto that directly with very little ceremony.
- Caching matters a lot. Because REST responses live at predictable URLs, standard HTTP caching (CDNs, browser cache, reverse proxies) works with no extra effort. GraphQL's single endpoint makes this kind of caching harder to get for free.
- Public, third-party APIs. Fixed, well-documented endpoints are easier for outside developers to reason about, debug with plain curl, and integrate against without learning a query language.
- Small teams without dedicated API tooling. REST needs less infrastructure to stand up: no schema layer, no resolver architecture, no query-cost analysis to worry about.
Where GraphQL tends to win
- Multiple clients with different data needs. A mobile app, a web dashboard, and a smartwatch app can each ask for exactly the fields they need from the same schema, instead of the backend team maintaining
/users/42?fields=summarystyle workarounds or several bespoke endpoints. - Deeply nested or relational data. Fetching a user, their last five orders, and each order's line items is one GraphQL query. In REST, that's often three or four round trips, or a bespoke aggregate endpoint built just for this one screen.
- Fast-moving frontend teams. Because the client controls the query shape, frontend engineers can often ship new UI without waiting on a backend change to add a new endpoint or field.
- Strong typing across the stack. GraphQL's schema doubles as living documentation and can generate client-side types automatically, which cuts down on a whole category of "the API changed and nobody told us" bugs.
The costs people tend to underweight
GraphQL's flexibility is also its main operational cost. A naive resolver can let a client request unbounded nested data and accidentally hammer your database — this is why production GraphQL APIs need query complexity limits, depth limiting, and often a dedicated data-loading layer to avoid the N+1 query problem. None of that is hard, but it is extra, and it needs someone who understands it.
REST's cost shows up differently: as an app grows, you tend to accumulate either overfetching (the client gets fields it doesn't need) or a sprawl of narrow, purpose-built endpoints (/users/42/dashboard-summary) that exist only to avoid overfetching. Neither is fatal, but both add up over the life of a project.
They're not mutually exclusive
It's common — and reasonable — to run both inside the same organization: a public REST API for third-party integrators who expect standard HTTP conventions, and an internal GraphQL layer serving your own web and mobile apps where flexibility matters more than public predictability. Some teams even put a GraphQL layer in front of existing REST services as an aggregation step, rather than treating it as a full replacement.
The actual decision
Choose REST by default if your domain is simple, your caching strategy relies on HTTP, or you're building for external consumers who'll appreciate predictable, browsable endpoints. Choose GraphQL if you're serving multiple clients with different data shapes, your data is heavily relational, and you have the appetite to set up query limits and a proper data-loading layer. Neither choice is permanent — plenty of production systems have migrated in both directions once their actual usage patterns became clear.