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.
Many endpoints versus one graph
Section titled “Many endpoints versus one graph”A REST API models resources as URLs. To build a profile page you might call three:
GET /users/42GET /users/42/postsGET /posts/99/commentsThe 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 Over- and under-fetching
Section titled “Over- and under-fetching”This is where the difference is felt day to day.
- With REST,
GET /users/42returns 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.
A side-by-side comparison
Section titled “A side-by-side comparison”| Concern | REST | GraphQL |
|---|---|---|
| Endpoints | Many, one per resource | One graph endpoint |
| Response shape | Fixed by the server | Chosen by the client |
| Over/under-fetching | Common | Avoided by design |
| Versioning | Often /v1, /v2 URLs | Evolve the schema; deprecate fields |
| HTTP caching | Easy — URLs cache well | Harder — needs app-level or persisted-query caching |
| Discoverability | OpenAPI/docs, by convention | Introspection built into the schema |
| Learning curve | Familiar to most teams | Schema, resolvers, and N+1 to learn |
Versioning
Section titled “Versioning”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.
The caching trade-off
Section titled “The caching trade-off”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.
See the round-trip difference
Section titled “See the round-trip difference”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.
When each one fits
Section titled “When each one fits”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.