API design comparison

REST vs GraphQL

Two different ways to design an HTTP API. Choosing between them is one of the most discussed architectural decisions of the last decade — and one of the most common topics in code reviews and system-design interviews.

TL;DR

  • REST exposes resources at stable URLs (one URL per resource type). Clients call multiple endpoints to assemble what they need. Great for simple, cacheable, public APIs.
  • GraphQL exposes a single endpoint with a typed schema. Clients write a query describing exactly the data they want. Great for product APIs powering many frontends with different data needs.
  • Neither is universally better. The choice depends on your client diversity, caching needs, schema evolution discipline, and team experience.

Side-by-side comparison

AspectRESTGraphQL
ShapeMultiple URL endpoints per resourceSingle endpoint, query-driven
Data fetchingFixed response per endpoint; over- or under-fetching commonClient picks exact fields → no over-fetching
Verbs / methodsHTTP verbs: GET, POST, PUT, PATCH, DELETEQuery, Mutation, Subscription (over POST or WebSocket)
Type systemOptional (OpenAPI / JSON Schema)Built-in, mandatory schema (SDL)
Versioning/v1/, /v2/ in URL or headerSchema evolution + @deprecated
CachingHTTP caching out of the box (CDN-friendly)Normalised client caches; harder on the wire
Error handlingHTTP status codes (4xx / 5xx)Always 200 OK; errors in "errors" array
N+1 problemClient orchestrates calls (front-end sees it)Resolvers expose it on the server (DataLoader pattern)
ToolingMature: Swagger, Postman, curlStrong: GraphiQL, Apollo Studio, codegen
Learning curveLow — HTTP knowledge is enoughHigher — schema design, resolvers, federation
Best forPublic APIs, partner integrations, file uploadsProduct APIs, mobile + web sharing one backend

Code side-by-side

Fetching a user with their last 5 orders:

REST (often 2 calls)

GET /api/users/42
→ { "id": 42, "name": "Alice", "email": "..." }

GET /api/users/42/orders?limit=5
→ [
    { "id": 901, "total": 42.00, "createdAt": "..." },
    { "id": 887, "total": 19.50, "createdAt": "..." },
    ...
  ]

GraphQL (one call)

POST /graphql
{
  user(id: 42) {
    name
    email
    orders(limit: 5) {
      id
      total
      createdAt
    }
  }
}

When to use REST

  • Public API consumed by third parties. REST is the lingua franca — every language has an HTTP client; every developer knows GET/POST.
  • You need HTTP caching. Static-ish resources cached at the CDN edge are dramatically cheaper than running every query through your servers.
  • File uploads / downloads. GraphQL handles these awkwardly; multipart uploads via REST are simpler.
  • Webhooks and event delivery. Outbound events to partner systems are almost always REST POST callbacks.
  • Small team, no GraphQL experience. REST has lower ramp-up cost and fewer footguns.

When to use GraphQL

  • Multiple clients with different data needs. Mobile, web, and partner apps can each ask for what they need without separate endpoints.
  • Aggregating multiple backend resources. A GraphQL layer becomes a thin orchestration on top of microservices.
  • Strong schema discipline desired. The mandatory schema becomes the contract between teams; codegen produces typed clients automatically.
  • Rapid product iteration. Adding fields without releasing new endpoints reduces deploy coupling.
  • Federated architecture. Multiple teams own subgraphs that compose into one supergraph (Apollo Federation, Hot Chocolate, etc.).

English phrases engineers use

REST conversations

  • "We expose an endpoint at /api/users/:id."
  • "This returns a 404 if the resource is missing."
  • "The response is idempotent — same input, same output."
  • "Let's version the API behind /v2/."
  • "The endpoint is over-fetching — the mobile app only needs three of the twelve fields."

GraphQL conversations

  • "We have a schema-first approach — the SDL is the source of truth."
  • "The resolver fetches related orders from the orders service."
  • "We're hitting N+1 on the comments resolver — let's add a DataLoader."
  • "The field is deprecated with @deprecated(reason: ...) — clients have 60 days to migrate."
  • "This is a breaking change on the schema — we'll need a coordinated rollout."

Quick decision tree

  • Public API consumed by external partners → REST
  • Need aggressive HTTP/CDN caching → REST
  • One backend, many frontends with different data needs → GraphQL
  • Mobile-first product where round trips hurt → GraphQL
  • Many microservices, want a single client API → GraphQL (Federation)
  • Team has no GraphQL experience and a tight deadline → REST
  • Need file uploads → REST (or hybrid)

Common Mistakes and Trade-offs

A remarkably common mistake teams make when evaluating REST versus GraphQL is prioritizing initial complexity reduction over long-term maintainability. Many developers, especially those new to API design, instinctively choose GraphQL because it looks simpler – a single endpoint, no need to define multiple resource representations. However, this often leads to poorly designed schemas that attempt to encompass every possible data requirement, resulting in massive, monolithic queries and eventual performance bottlenecks. The initial reduction in effort is quickly outweighed by the increased debugging time, complex query optimization, and difficulty in evolving the schema as business needs change. It's a classic case of solving one problem (complex client requirements) with another that's just as challenging – or more so – without any fundamental architectural improvements.

At scale, GraphQL introduces significant operational overhead related to caching. While REST APIs can leverage standard HTTP caching mechanisms effectively, GraphQL's ability to return deeply nested data structures necessitates a much more sophisticated and granular caching strategy. Each field query is essentially a separate request, and the impact of cache invalidation becomes exponentially complex as the number of resolvers (the functions that fetch the data for each field) grows. Without careful consideration of caching layers – often involving server-side proxies or dedicated GraphQL caching solutions – performance can degrade rapidly under heavy load, turning what was initially a speed advantage into a significant operational burden. This isn't about simply adding a CDN; it's about fundamentally rethinking how data is served and cached.

The persistent misconception that GraphQL is 'always better' for new applications is dangerously misleading. While GraphQL excels at scenarios with diverse client requirements – particularly mobile apps or those needing to fetch data from multiple, disparate sources – REST remains the superior choice when simplicity and straightforwardness are paramount. For basic CRUD operations on a relatively static dataset, a well-designed REST API built with clear resource definitions is often faster to develop, easier to understand, and less prone to errors. Over-engineering a simple application with GraphQL introduces unnecessary complexity that adds no value and increases the risk of introducing bugs. It's about choosing the right tool for the job, not forcing a single technology onto every situation.

Migrating from a REST API to GraphQL – or, more commonly, combining them strategically – requires careful planning beyond simply swapping out endpoints. Often, teams attempt a wholesale conversion, leading to significant disruption and rework. A more pragmatic approach involves identifying distinct 'domains' within the application where GraphQL's strengths align with the existing REST infrastructure. For example, public-facing APIs might remain RESTful while internal microservices leverage GraphQL for optimized data retrieval. Furthermore, consider using GraphQL as a layer on top of existing REST services; this allows you to gradually adopt GraphQL without completely rewriting your core system and provides a fallback mechanism if issues arise. The key is not to treat them as mutually exclusive technologies but rather as complementary tools within a broader architectural strategy.

Frequently asked questions

What is the main difference between REST and GraphQL in plain English?

REST organises an API around resources (URLs like /users/42), and clients call separate endpoints to fetch related data. GraphQL exposes a single endpoint and lets clients describe exactly the shape of the data they want in one query — including nested resources.

When should I use REST instead of GraphQL?

Use REST when your API is simple and resource-shaped (CRUD on a few entities), when HTTP caching matters (REST plays well with CDNs), when consumers are external partners who expect a conventional REST API, or when your team has no in-house GraphQL experience.

When is GraphQL the better choice?

Pick GraphQL when clients (mobile, multiple frontends) need to compose data from many backend resources and want to avoid over-fetching; when you have a typed schema you want to share across teams; or when you need strong client-side tooling (auto-generated types, IDE autocomplete).