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:

  1. How many distinct clients consume this API, and do you control them? One team, one client: REST. Many teams, external clients: GraphQL.

  2. 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.

  3. 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.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Idempotency: The API Property Most Backend Devs Forget Until It's Too Late

Non-idempotent APIs combined with retry logic are a production incident waiting to happen. Adding idempotency keys is not a nice-to-have for payment APIs — it is a correctness requirement for any operation that should not be executed twice.

Read more

Why “Simple Features” Are Often Not Simple

“It’s just a small feature” is one of the most expensive sentences in software. What looks simple on the surface often hides layers of complexity underneath.

Read more

How to Handle a Failing Software Project Professionally

“Something feels off… but no one wants to say it yet.” That quiet moment is where professionalism actually begins.

Read more

What Microservices Actually Mean and Why Most Teams Get It Wrong

Microservices are not small services or REST APIs with separate databases — they are a specific organizational and technical pattern with precise prerequisites. Most teams adopt the aesthetics without the substance.

Read more