All articles
169 articles · updated weekly See our Tools
All articles
Comparisons

GraphQL vs REST: when GraphQL is worth it (and when it isn't)

Overfetching, N+1, caching, and operational complexity: why most APIs don't need GraphQL, and when it actually makes sense.

GraphQL vs REST: when GraphQL is worth it (and when it isn't)
COVER · Comparisons

You read the hype, watched the GraphQL conf talks, and now you're convinced your REST API is outdated. Before migrating everything, it's worth understanding why most APIs I've seen — including the ones that scale — still run on REST without trouble. GraphQL solves real problems, but it usually solves problems you don't have yet.

What overfetching and underfetching actually mean (and why it matters)

Overfetching is when the endpoint returns more fields than the client needs. Underfetching is when it returns less, and the client has to make multiple requests to build a complete response.

These problems exist. But before assuming GraphQL is the solution, it's worth measuring how much they're actually costing you.

REST overfetching is usually solved with simple query params:

GET /users/42?fields=id,name,email

It's not as elegant as a GraphQL query, but it works, and any developer understands it without reading documentation. If the extra payload is 200 bytes, it probably doesn't justify switching the entire stack.

Underfetching is the more legitimate case for GraphQL. If the frontend needs data from users, orders, and products to render a screen, REST needs three requests or a specific composition endpoint. GraphQL solves this with a single query. That gain is real — especially on mobile with poor connectivity.

The issue is that most applications have screens with reasonably predictable data. If you control both frontend and backend, you can create screen-specific endpoints (BFF — Backend for Frontend) that solve underfetching without GraphQL.

The N+1 problem (and how it shows up on both sides)

The N+1 problem appears when you fetch a list of entities and then make a separate query for each item in the list. It's the nightmare of any poorly configured ORM.

In GraphQL, N+1 is almost a built-in trap. If you have a users resolver that returns 50 users, and each user has an orders resolver, you'll fire 50 order queries — unless you implement DataLoader or some batching strategy.

// Resolver without DataLoader — N+1 guaranteed
const resolvers = {
  User: {
    orders: (user) => db.orders.findAll({ where: { userId: user.id } })
  }
}

In REST, N+1 usually shows up in a poorly implemented composition endpoint, but it's more visible and easier to diagnose because it's concentrated in one place.

DataLoader fixes N+1 in GraphQL, but it's one more thing to implement, test, and maintain. It's complexity that didn't exist before.

Caching: where REST has a clear advantage

This is one of the points where REST wins unambiguously: HTTP's cache model was built for GET requests with static URLs.

A GET /products/42 is cacheable by CDN, by the browser, by a reverse proxy. You configure Cache-Control, ETag, and most infrastructure already knows what to do.

GraphQL sends everything via POST to a single endpoint (/graphql). POST is not cacheable by default. You can use persisted queries, Apollo Client with normalized caching, or HTTP GET for simple queries — but these are all workarounds for a problem REST doesn't have.

If your API serves public content and you want to put a CDN in front of it, GraphQL will require extra work. REST will work naturally.

The complexity nobody mentions in intro posts

GraphQL isn't just "writing more flexible queries". It's an entire system you need to operate:

Schema and mandatory types. You need to define a strongly typed schema before implementing anything. It's good for documentation, but it's real overhead at the start.

Query security. A client can send a deep, circular query that takes down your server:

{
  users {
    friends {
      friends {
        friends {
          friends { id }
        }
      }
    }
  }
}

You need to implement depth limiting, query complexity analysis, and rate limiting by complexity. REST has the same problem in theory, but in practice it's much harder to trigger accidentally.

Monitoring. With REST, you monitor endpoints. GET /users with high latency — you know where to look. With GraphQL, everything goes to /graphql. You need per-operation tracing (Apollo Studio, Grafana with specific plugins) to understand what's slow.

Errors. GraphQL returns HTTP 200 even when the query partially failed. A field can be null either because it doesn't exist or because the resolver threw an exception. You need to look at the errors field in the response:

{
  "data": { "user": null },
  "errors": [{ "message": "User not found", "path": ["user"] }]
}

This breaks the HTTP status code mental model that every developer already knows. For quick reference on REST status code semantics, I use HTTP Status Codes when I need to confirm the correct meaning of 204 vs 200 with an empty body.

When GraphQL actually makes sense

I'm not against GraphQL. There are contexts where it's the right choice:

Multiple clients with different needs. If you have a mobile app, a web app, and an external partner, all consuming data in radically different formats, a GraphQL schema with flexible queries can be better than maintaining three versions of REST endpoints.

Product with highly dynamic UI. User-configurable dashboards, report builders, cases where the client genuinely doesn't know in advance which fields it will need. This is the use case where GraphQL's flexibility pays off.

Large team with independent frontends. When the iOS team, Android team, and web team work relatively independently and need to iterate quickly without depending on the backend for each new field.

Microservice federation. GraphQL Federation is genuinely powerful for exposing a unified data graph that lives in distinct services. If you have 10 microservices and need a unified interface for the frontend, Federation is an elegant solution.

When REST is enough (most cases)

If your API has well-defined resources with predictable CRUD operations, REST works. If you control the frontend, you can create endpoints specific to your needs. If the team has no GraphQL experience, the learning and operational cost doesn't pay off quickly.

See the post REST: principles and best practices for a practical guide on structuring a REST API that doesn't turn into a mess over time — before deciding you need GraphQL because your current REST API is poorly designed.

A poorly structured REST API isn't an argument for adopting GraphQL. It's an argument for better structuring the REST API.

Frequently asked questions

Is GraphQL faster than REST?

Not necessarily. On slow networks, a GraphQL query that replaces multiple requests can be more efficient. But on simple requests, REST with CDN caching will be faster than any GraphQL solution. Performance depends on the use case — there's no universal answer.

Is GraphQL the future of APIs?

GraphQL has existed since 2015, was open-sourced by Facebook that same year, and is widely used. But the "future" of APIs in 2026 looks more distributed: REST still dominates in raw volume, GraphQL has a consolidated space in products with complex UI, and gRPC grows for internal microservice communication. None of the three will disappear.

Can I use GraphQL and REST together in the same application?

Yes, and it's common. An architecture I've seen work well: GraphQL for the product interface (where flexibility matters), REST for external integrations and webhooks (where predictability and caching matter), and gRPC for internal service-to-service communication. There's no obligation to pick a single paradigm.

Is the N+1 problem in GraphQL serious in production?

It's serious if you don't address it. DataLoader is the industry standard for solving this in Node.js, and equivalents exist in other languages. The point is: you need to know the problem exists and actively implement the solution. In REST, the equivalent would be a listing endpoint that makes inefficient joins — also serious, but generally easier to catch in code review.

REST or GraphQL: the right decision depends on the problem

My position after working with both in production: start with REST. If you're solving one of the specific problems GraphQL was designed for — multiple clients with divergent needs, highly dynamic UI, microservice federation — the investment is worth it. Otherwise, you're adding operational complexity to solve a problem you don't have.

Most APIs that become slow, hard to maintain, or inconsistent didn't get that way because of the paradigm. They got that way because of bad design decisions you can make just as badly in GraphQL or in REST.

RD
Author
Rafael Duarte
Backend developer with a background in fintech and B2B SaaS — worked on teams that scaled APIs from zero to millions of requests. Carries enough production scars to hold strong opinions on tools, patterns and architecture decisions. Not an academic: he read the UUID RFC when he had to choose between v4 and v7 for a write-heavy table.
View profile