REST and GraphQL are the two most common ways to design web APIs. Both work over HTTP; they differ in who decides the shape of the data and how requests are organised.

REST in brief

Resources live at URLs; HTTP methods express actions:

GET    /api/orders/42
GET    /api/orders/42/items
POST   /api/orders
PATCH  /api/orders/42
DELETE /api/orders/42

The server defines what each endpoint returns. Status codes, caching headers and the HTTP semantics everyone knows do a lot of work.

GraphQL in brief

One endpoint (POST /graphql); the client sends a query describing exactly the data it wants:

query {
  order(id: 42) {
    status
    customer { name email }
    items { name quantity price }
  }
}

The server has a typed schema; resolvers fetch each field. Mutations change data; subscriptions push updates.

Comparison

REST GraphQL
Data shape Fixed per endpoint Chosen per query
Over/under-fetching Common (extra fields, multiple calls) Avoided
HTTP caching / CDN Natural (GET + Cache-Control) Harder (POST, one endpoint); needs persisted queries or app-level caching
Versioning Often /v1, /v2 Evolve the schema; deprecate fields
Errors HTTP status codes Usually 200 with an errors array
Tooling OpenAPI, curl, any HTTP client Introspection, GraphiQL, code generation
Server complexity Low Higher (resolvers, N+1 queries, query cost)
File uploads Simple Needs extensions/workarounds
Learning curve Low Moderate

Where GraphQL shines

  • Many clients (web, iOS, Android) needing different slices of the same data.
  • Complex, connected data where REST would need many round trips.
  • Fast-moving front-end teams who want to add fields without backend changes.
  • Aggregating several backend services behind one API (“backend for frontend”).

Where REST shines

  • Public APIs consumed by third parties — familiar, easy to document and cache.
  • Simple CRUD services.
  • Heavy use of HTTP caching and CDNs.
  • File-centric APIs (uploads, downloads).
  • Teams wanting minimal moving parts.

Performance pitfalls

GraphQL:

  • N+1 queries — resolving items for 100 orders can trigger 100 database queries. Use batching (DataLoader) and query planning.
  • Expensive queries — clients can request deeply nested data. Enforce depth limits, complexity/cost limits, timeouts and pagination; consider persisted (allow-listed) queries in production.

REST:

  • Chatty clients — a screen needing 6 endpoints means 6 round trips; mitigate with compound endpoints or ?include= parameters.
  • Overfetching large payloads on mobile; add sparse fieldsets (?fields=).

Security considerations

  • Authentication and authorisation apply per field/resolver in GraphQL, not just per endpoint — easy to miss.
  • Disable or restrict introspection in production if your schema is not public.
  • Rate-limit by query cost rather than request count for GraphQL.
  • For both: validate input, paginate, and log abuse. See API security best practices and API rate limiting.

Other options

  • gRPC — binary, schema-first (Protocol Buffers), fast, streaming; great between internal services, less suited to browsers.
  • tRPC — end-to-end type-safe APIs for TypeScript monorepos, no schema files.
  • JSON:API / OData — conventions on top of REST.

Decision guide

  • Public or partner API, simple resources → REST (with OpenAPI docs).
  • Rich product UI across several clients, complex data graph → GraphQL.
  • Internal service-to-service → gRPC or REST.
  • Full-stack TypeScript app → tRPC or REST.

Whatever you choose, deploy it behind a reverse proxy with HTTPS and sensible limits — see Node.js deployment with Nginx and FastAPI with Uvicorn.

  • APIs
  • REST
  • GraphQL