REST vs GraphQL — When I Recommend Each to Clients
by Arif Ikhsanudin, Backend Developer
The over-fetching problem is real, but only for some teams
Your mobile team is complaining that the /users/:id endpoint returns 40 fields and they only need 6. The web team needs those 40 fields plus some relationships. You have two options: version your REST endpoints, add query parameters to filter fields, or switch to GraphQL and let clients declare exactly what they need. The question is which of these is worth its complexity, and for whom.
Where REST is clearly the right answer
REST wins when you have a small, known, controllable set of clients — typically a single-team frontend plus a handful of integration partners. In this context, over-fetching is not a real problem. You add a field to the response, clients that need it use it, clients that do not ignore it. The "problem" GraphQL solves is not a problem for you.
REST's operational story is also significantly simpler. HTTP caching works out of the box — Cache-Control, ETags, and CDN caching all apply naturally to GET endpoints. With GraphQL, all requests are POST to a single endpoint, which breaks HTTP-level caching entirely. You need client-side caching (Apollo Client, urql) or persisted queries to get equivalent cache behavior, and you are now maintaining those systems.
# REST — clean, cacheable, auditable in logs
GET /api/v1/orders/12345
Authorization: Bearer <token>
# What shows up in your logs, your CDN, your monitoring
# GET /api/v1/orders/12345 200 45ms
# GraphQL — same data, different story for caching/observability
POST /graphql
{"query":"query { order(id: 12345) { id status total lineItems { sku qty } } }"}
# What shows up in your logs
# POST /graphql 200 45ms (every request looks the same)
The log problem is not theoretical. When you are debugging a production incident at 2am and your observability tool shows 50,000 POST requests to /graphql all returning 200, distinguishing which query types are causing latency requires either custom logging middleware or APM tooling with GraphQL-specific support (Apollo Studio, DataDog's GraphQL tracing). This is solvable but it is cost you did not have with REST.
Where GraphQL is genuinely worth it
GraphQL earns its complexity when two conditions are both true: you have multiple clients with divergent data needs, and you do not control all of those clients.
A public API consumed by hundreds of third-party developers is the canonical case. Each developer's application needs different fields. With REST, you either return everything (over-fetching), maintain per-client endpoint variants (maintenance hell), or implement a sparse fieldset parameter (reinventing GraphQL badly). With GraphQL, the schema is the contract, clients declare their own data requirements, and you do not need to anticipate every combination.
The Facebook/GitHub/Shopify use case is real. These are platforms with external developers building on top of an API they cannot modify. GraphQL is the right design for that model.
# GraphQL schema — the contract is explicit and self-documenting
type Query {
order(id: ID!): Order
orders(status: OrderStatus, limit: Int = 20): [Order!]!
}
type Order {
id: ID!
status: OrderStatus!
total: Money!
customer: Customer!
lineItems: [LineItem!]!
shippingAddress: Address
}
# Mobile client — declares exactly what it needs
query MobileOrderSummary($id: ID!) {
order(id: $id) {
id
status
total { amount currency }
}
}
# Web client — declares more
query WebOrderDetail($id: ID!) {
order(id: $id) {
id status total { amount currency }
customer { name email }
lineItems { sku qty unitPrice }
shippingAddress { street city country }
}
}
The N+1 query problem is worth naming explicitly. GraphQL's nested resolution model means that resolving a list of orders, each with a customer, will execute one query for orders and N queries for customers unless you implement DataLoader (batching) or eager loading. This is a solvable problem but requires deliberate architecture. Junior teams new to GraphQL will hit this in production before they instrument for it.
The hybrid model that often makes sense
For most product companies — a SaaS with a web app, a mobile app, and a handful of integration partners — the right answer is often: REST for everything except the one or two surfaces where GraphQL's flexibility is valuable.
Your public-facing product API is REST: predictable URLs, HTTP caching, simple auth, easy to test with curl. Your internal data-heavy frontend (dashboards, reporting, complex filtering) is GraphQL: the client can compose exactly the query it needs without requiring backend changes for each new filter combination.
This is not a compromise — it is recognizing that the two tools solve different problems. REST is a resource model. GraphQL is a query language. They are not competing implementations of the same idea.
How I actually make the recommendation
Three questions determine the answer:
-
How many distinct clients consume this API, and do you control them? One team, one client: REST. Many teams, external clients: GraphQL.
-
Does your frontend team change their data requirements faster than your backend can ship REST endpoint changes? If the frontend is blocked waiting for backend for routine data shape changes, GraphQL buys you autonomy.
-
Do you have the operational tooling for GraphQL? APM with GraphQL awareness, persisted queries for production, DataLoader for N+1 prevention. If you do not, REST is the safer choice until you do.