Skip to content

GraphQL vs REST

REST has served the web well for two decades, and most teams adopting GraphQL are migrating from it. So the honest question is not “which is better” but “what does each one optimize for, and which trade-offs am I choosing?” This lesson lays the two side by side.

A REST API models resources as URLs. To build a profile page you might call three:

GET /users/42
GET /users/42/posts
GET /posts/99/comments

The equivalent GraphQL request is one query to one endpoint, walking from the user to their posts to each post’s comments in a single round trip:

{
user(id: "42") {
name
posts {
title
comments {
body
}
}
}
}
flowchart TB
  subgraph REST["REST — three round trips"]
    direction LR
    R1["GET /users/42"] --> R2["GET /users/42/posts"] --> R3["GET /posts/99/comments"]
  end
  subgraph GQL["GraphQL — one round trip"]
    direction LR
    G1["POST /graphql"] --> G2["user -> posts -> comments"]
  end
Three REST round trips collapse into one GraphQL query.

This is where the difference is felt day to day.

  • With REST, GET /users/42 returns a fixed payload. If you only need the name you still receive the whole record (over-fetching); if you need related posts you make another call (under-fetching).
  • With GraphQL, you list exactly the fields you want, across related types, and get them in one response. Neither problem arises by default.
ConcernRESTGraphQL
EndpointsMany, one per resourceOne graph endpoint
Response shapeFixed by the serverChosen by the client
Over/under-fetchingCommonAvoided by design
VersioningOften /v1, /v2 URLsEvolve the schema; deprecate fields
HTTP cachingEasy — URLs cache wellHarder — needs app-level or persisted-query caching
DiscoverabilityOpenAPI/docs, by conventionIntrospection built into the schema
Learning curveFamiliar to most teamsSchema, resolvers, and N+1 to learn

REST APIs commonly version through the URL — /v1/users, then /v2/users. GraphQL leans on continuous evolution instead: add new fields freely (they are opt-in, so old clients are unaffected), and mark old fields with @deprecated to guide clients off them over time. There is rarely a hard version bump.

This is GraphQL’s most real cost. REST’s GET /posts/99 is a stable URL that browsers, CDNs, and proxies cache for free. GraphQL queries usually arrive as POST requests with the query in the body, so that layer of HTTP caching does not apply out of the box. Teams recover it with techniques like persisted queries (sending a stable hash instead of the full query text) and normalized client caches (such as those in Apollo Client or urql). It is solvable, but it is not free.

The runner below executes a single GraphQL query that, in a REST world, would have needed three separate calls. Notice how one request returns a nested, ready-to-render structure.

JavaScript

Reach for REST when your API is resource-shaped and cache-heavy (think public, read-mostly content served through a CDN), when consumers are simple, or when the team already ships REST well and the extra moving parts would not pay off.

Reach for GraphQL when many different clients need different slices of the same data, when views aggregate several related resources, when product teams iterate fast and want to add fields without coordinating endpoint releases, or when strong typing and introspection across the API surface bring real leverage.

Which problem does GraphQL avoid by design that REST commonly hits?
What is the most commonly cited trade-off of GraphQL compared with REST?
How does GraphQL usually handle API evolution instead of URL versions like /v1 and /v2?
Which scenario fits REST better than GraphQL?