NodeJSGraphQL vs REST

GraphQL vs REST

GraphQL and REST are two ways to design an API, and the choice is real — not a matter of one being universally "better." REST exposes many resource URLs with fixed response shapes; GraphQL exposes one endpoint and lets the client compose the shape it needs. Each makes different things easy and different things hard. This page sets them side by side on fetching, caching, errors, versioning, and tooling, looks at what each does well and badly, and gives an honest rule for choosing — including the increasingly common answer: use both.

The same request, two paradigms

Text
Goal: show a user, their last 3 orders, and each order's item names.

REST — multiple round-trips, fixed shapes (often over-fetching):
   GET /users/42                  → {id, name, email, address, ...30 fields}
   GET /users/42/orders?last=3    → [{id, total, ...}, ...]
   GET /orders/o1/items           → [...]   ┐
   GET /orders/o2/items           → [...]   ├ N more calls (under-fetching)
   GET /orders/o3/items           → [...]   ┘

GraphQL — one request, exact shape:
   POST /graphql
   { user(id:"42") { name  orders(last:3) { total  items { name } } } }
   → exactly those fields, one round-trip
REST trades many fixed endpoints; GraphQL trades one flexible endpoint — the split shapes everything else
The core difference is structural. **REST** models the API as **resources**, each with a URL and a fixed representation; you compose data by calling several endpoints, often getting back more fields than you need (**over-fetching**) and making multiple sequential calls to assemble a screen (**under-fetching** / waterfalls). **GraphQL** models the API as one **graph** behind a single endpoint; the client sends a query describing exactly the fields it wants — nested across what would be several REST resources — and gets precisely that in one round-trip. Almost every other trade-off below flows from this one decision: many simple, cacheable, fixed endpoints versus one expressive, harder-to-cache, flexible one.
Side by side

Concern

REST

GraphQL

Endpoints

Many URLs, one per resource

One (POST /graphql)

Response shape

Fixed by the server

Chosen by the client

Over/under-fetching

Common

Eliminated by design

Round-trips for nested data

Often several

One

HTTP caching

Excellent — built-in (GET + URLs + Cache-Control)

Hard — needs app-level / client caching

Typed contract

Optional (OpenAPI/Swagger add-on)

Built-in & mandatory (the schema)

Versioning

Usually /v2/ URLs

Versionless — add fields, deprecate old ones

File uploads / binary

Natural

Awkward (needs a spec/multipart workaround)

Error model

HTTP status codes

200 + errors[] array (usually)

Learning curve / setup

Low — it's just HTTP

Higher — schema, resolvers, server

Where each genuinely wins

Text
GraphQL shines when:                      REST shines when:
   • many clients with different data needs    • simple CRUD / resource-shaped data
     (web + iOS + Android) hit one API          • HTTP caching/CDNs matter a lot
   • screens aggregate many related            • public API consumed by unknown clients
     resources (avoid waterfalls)              • file uploads / streaming / binary
   • rapidly evolving frontend product         • you want minimal moving parts
   • a strongly-typed client contract           • the team already knows REST cold
     drives codegen & autocompletion
GraphQL fits many-client, aggregation-heavy, fast-evolving products; REST fits simple, cacheable, resource-shaped APIs
**GraphQL pays off** when multiple clients (web, iOS, Android) with diverging data needs share one backend, when screens stitch together many related resources (eliminating waterfalls), and when a fast-moving frontend benefits from a typed contract that drives code generation and autocompletion. **REST pays off** for simple, resource-shaped CRUD, when HTTP caching and CDNs are central to your performance story, for public APIs consumed by unknown clients (familiar, no query-cost surprises), and for file uploads/streaming. The honest tie-breaker: GraphQL's flexibility shifts complexity *to the server* (caching, query-cost limits, the [N+1 problem](/nodejs/graphql-resolvers)). If you don't have the problems GraphQL solves, REST's simplicity is a feature, not a limitation.
The complexity GraphQL moves to the server
GraphQL doesn't remove complexity — it relocates it to your server: caching, query-cost control, and N+1
Be clear-eyed about the costs you take on by choosing GraphQL. **Caching** no longer comes free from HTTP — every operation is a `POST` to one URL, so you cache at the resolver/field layer or with a client cache (Apollo Client) instead of letting a CDN do it. **Query cost is unbounded** — a client can request arbitrarily deep, expensive queries, so you *must* add depth and complexity limits, timeouts, and (for public APIs) persisted/allow-listed queries to prevent abuse. The **[N+1 problem](/nodejs/graphql-resolvers)** appears naturally and demands DataLoader batching. And there's real **setup overhead**: schema, resolvers, server, tooling. None of this is prohibitive, but it's work REST simply doesn't require — choose GraphQL because its benefits outweigh *these specific costs* for your case, not by reputation.
You don't have to choose globally
Many systems run both — GraphQL for the app-facing aggregation layer, REST for webhooks, uploads, and public endpoints
The framing as an either/or is often false. Plenty of production systems run **both**: a GraphQL endpoint serves the rich, aggregation-heavy needs of first-party web and mobile apps, while REST endpoints handle [webhooks](/nodejs/webhooks), file uploads, simple public integrations, and health checks — each used where it's strongest, frequently in the *same* [Express](/nodejs/express-intro) app (GraphQL mounted at `/graphql` alongside REST routes). GraphQL can even sit as a **gateway** in front of existing REST microservices, composing them into one graph for clients without rewriting the services. Pick per use case, not per company. The skills transfer: both are just HTTP underneath, and a well-designed schema or a well-designed set of resources reflects the same clear thinking about your domain.
Next
Now go the other way — call APIs you don't own: [Consuming Third-Party APIs](/nodejs/consuming-apis).