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
itemsfor 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.