Home / Blog / REST vs GraphQL: when GraphQL actually wins

REST vs GraphQL: when GraphQL actually wins

GraphQL gets pitched as the default for modern APIs. On most projects REST ends up winning. Here's where GraphQL really pays off.

GraphQL is the query language Facebook open-sourced in 2015. It’s been marketed as “the future of modern APIs” for a decade. Most developers have “GraphQL experience” on their resume. But the majority of production APIs are still REST.

My breakdown: across 20+ API projects I’ve only reached for GraphQL on two of them. REST was the right call on the other 18. Here’s where GraphQL genuinely wins.

Where GraphQL pulls ahead

1. When client needs are variable and not known upfront.

A mobile app has many screens. On the profile screen you show the user name and avatar. On a listing screen, name, avatar, post count. On the detail screen, everything plus the last five posts.

With REST, you write one endpoint per screen, or you over-fetch (return the full user on every endpoint and let the client pick).

With GraphQL the client picks the fields it wants from a single endpoint. When a new screen needs different fields, the backend doesn’t change.

Real example: Facebook’s news feed. Thousands of view variants, each needing a different data shape. REST gets you death by endpoint.

2. Aggregating multiple data sources.

Your backend has three services: user, post, comment. The client wants author info plus the last five comments on a post. That’s three round trips with REST.

With GraphQL, one query:

query {
  post(id: 123) {
    content
    author { name avatar }
    comments(last: 5) { text author { name } }
  }
}

The GraphQL server aggregates the three services and returns a single response.

3. Rapid prototyping or iterative development.

At the startup stage the product shifts every sprint. New data requirements every two weeks. In REST every new endpoint is a backend change and a deploy. In GraphQL you add a field to the schema and it works.

That speed pays off early. Once the product stabilises, REST lets you squeeze more performance.

4. Complex nested data.

If your data nests five or six levels deep (user to posts to comments to replies to reactions), serving that in REST is N+1 hell. GraphQL, with the DataLoader pattern, solves it cleanly.

Six scenarios where GraphQL is the wrong call

1. Simple CRUD APIs.

An admin panel, a blog, a dashboard. Data shape is stable, queries repeat. REST is enough and it’s simpler.

2. File upload and streaming.

File upload in GraphQL is a multipart extension and not standardised. Streaming downloads are more natural in REST. Media-heavy APIs belong in REST.

3. When caching is critical.

REST GET endpoints work with HTTP-level caching (ETag, Cache-Control). You can put them behind a CDN, the browser caches them. GraphQL typically uses POST so that whole mechanism is gone. Cache ends up as a separate layer (Apollo Cache, Relay Cache).

4. Internal microservice communication.

Service-to-service calls are better served by gRPC or REST. GraphQL adds overhead (schema, resolvers). Internal APIs don’t benefit from that.

5. Public APIs and third-party consumers.

Stripe, Twilio, and every public API I use are REST. Reason: the learning curve is low, the tooling ecosystem is wide, OpenAPI and Swagger documentation is mature. A GraphQL public API raises the entry bar for external developers.

6. Small team, limited resources.

The GraphQL stack (Apollo Server, Relay, tooling) takes time to learn. For a team of two or three, REST has a much lower learning and maintenance cost.

The production complexity: authorisation

The quietly hard part of GraphQL: authorisation.

In REST, a rule for an endpoint is clear: “POST /api/admin/delete requires admin role”. One middleware check and you’re done.

In GraphQL a single query might ask for:

query {
  user(id: 123) {
    name         # anyone can see this
    email        # only the user themselves
    internalNotes # admins only
  }
}

Each field has its own rule. You need resolver-level authorisation, which is easy to get wrong. Forget one field and you have a security hole.

Fix: schema-aware authorisation libraries (Apollo’s @auth directive, and so on). That’s more complexity on top.

The N+1 query problem

GraphQL’s famous performance trap. A nested query, one DB call per item:

query {
  posts {        # 1 query (returns 10 posts)
    title
    author { name }   # 1 query per post = 10 queries
  }
}

11 queries total. As post count grows, the DB melts.

Fix: DataLoader. The author resolver batches and a single “WHERE id IN (…)” runs.

Apollo’s DataLoader automates it, but the code has to be written with discipline. REST doesn’t give you N+1 because you only optimise one endpoint.

Rate limiting is harder

REST rate limiting is simple: “max 100 requests per minute”.

In GraphQL, how much complexity can a single request contain? You need complexity scoring:

user(id: 123) {
  posts(first: 100) {      # complexity: 100
    comments(first: 50) {  # complexity: 100 * 50 = 5000
      ...
    }
  }
}

That one request consumes 5,100 “complexity points”. Complexity-based rate limiting is mandatory on public GraphQL APIs.

GitHub’s GraphQL API is a good model: “5,000 points per minute, every query consumes points by complexity”.

Tooling gap

REST tooling is extremely mature: OpenAPI and Swagger for documentation, Postman, Insomnia, curl, HTTPie. Everyone knows it, everyone uses it.

GraphQL tooling is good too (GraphQL Playground, Apollo DevTools, Hasura) but there’s a learning curve.

Migration is painful

Moving an existing REST API to GraphQL isn’t easy. Old clients still expect REST, new clients want GraphQL. Two parallel stacks.

A hybrid approach: GraphQL for one priority client, REST for the rest. But now maintenance doubles.

My decision matrix

Weighing GraphQL vs REST on a new API project:

| Factor | GraphQL | REST |
|——–|———|——|
| Public third-party API | – | OK |
| Internal microservice comms | – | OK |
| Mobile app, many screens | OK | – |
| Simple CRUD | – | OK |
| Admin panel | – | OK |
| Multi-source aggregation | OK | – |
| Small team | – | OK |
| File upload heavy | – | OK |
| Prototype, shifting requirements | OK | – |
| Strong HTTP caching needed | – | OK |
| Real-time (subscriptions) | OK | – |

On most projects the right column wins.

Takeaway

GraphQL is powerful in specific scenarios: mobile-first, many screens, multi-source aggregation, rapid iteration. It shouldn’t be the default.

REST is simple, mature, broadly tooled, and everyone knows it. On most APIs it’s the right tool. Moving to GraphQL should be justified by a real problem, not by following trends.

The two projects I built in GraphQL were both large-scale mobile apps with many screen variants. On the other 18, REST was correct. Which category is your project in?

Have a project on this topic?

Leave a brief summary — I’ll get back to you within 24 hours.

Get in touch