---
title: "REST vs GraphQL"
slug: "rest-vs-graphql"
category: "comparisons"
tags: ["comparisons", "rest", "graphql", "api", "backend"]
status: "stable"
last_updated: 2026-05-14
summary: "Pick REST for simple CRUD and CDN-cacheable reads; pick GraphQL for client-driven aggregation across many resources; consider tRPC inside a TypeScript monorepo."
related: ["[[backend/fastapi]]", "[[backend/postgres]]", "[[comparisons/typescript-vs-javascript]]", "[[comparisons/monorepo-vs-polyrepo]]", "[[frontend/nextjs]]", "[[coding/typescript]]"]
---

## Overview

Pick REST for any API that is mostly CRUD, mostly read, and benefits from HTTP-layer caching (CDN, conditional requests, ETags). Pick GraphQL when one client needs to assemble views from many resources at once and over-fetching or N+1 round trips would dominate latency. Consider tRPC as a third option when the same TypeScript repo owns both the client and the server; it removes both the schema overhead of GraphQL and the over-fetch problem of REST. The wrong choice is GraphQL on a small CRUD app or REST on a mobile app aggregating six entities per screen. See [[backend/fastapi]] for the REST rule set.

## When REST wins

REST is the right pick for the majority of public APIs and most internal services.

- HTTP caching is free: `Cache-Control`, `ETag`, and `Vary` headers let a CDN front the API and serve cached responses without touching the origin.
- Tooling is universal: every language ships an HTTP client; OpenAPI generates clients in 30+ languages.
- [[glossary/idempotent|Idempotency]] is well-understood at the verb level: `GET`, `PUT`, `DELETE` are idempotent; `POST` is not.
- Easier to debug: every endpoint is a URL; `curl`, browser dev tools, and HAR files just work.
- Cheap to ship a v1: pick a framework (FastAPI, Hono, Express), define routes, ship.
- Webhook-style integrations expect REST; GraphQL subscriptions are not the same thing.

## When GraphQL wins

GraphQL is the right pick when one client view needs data from many sources and over-fetching hurts.

- Mobile clients aggregating five-plus entities per screen: one round trip, exactly the fields the client needs.
- Federated graphs across teams: Apollo Federation or Hot Chocolate let each team own a subgraph; the gateway composes them.
- Strict schema with generated types end to end; the schema is the contract.
- Public APIs with diverse consumers who want different field subsets (GitHub, Shopify, Stripe).
- Real-time updates via subscriptions, paired with persistent connections.

The wrong reasons to pick GraphQL: "REST is old," "we want a single endpoint," or "the frontend team wants it." Those are not load-bearing reasons.

## Trade-offs at a glance

| Dimension     | REST                                     | GraphQL                                  |
| ------------- | ---------------------------------------- | ---------------------------------------- |
| HTTP caching  | Free with `GET`, `Cache-Control`, `ETag` | Hard; usually app-layer (Apollo, Relay)  |
| Pagination    | Cursor or offset, per endpoint           | Relay connection spec, schema-wide       |
| Over-fetching | Common; mitigated by sparse fieldsets    | Solved by query syntax                   |
| N+1 risk      | Per endpoint; obvious in logs            | High; needs DataLoader on every resolver |
| Versioning    | URL or header versioning                 | Schema evolution with deprecation        |
| Auth          | Per-endpoint middleware                  | Per-resolver; schema-wide is tricky      |
| File upload   | Native multipart                         | Multipart spec is an extension           |
| Tooling       | OpenAPI, Postman, curl                   | Apollo Studio, GraphQL Playground        |
| Subscriptions | Server-Sent Events or WebSockets         | Built-in subscription type               |
| Best client   | Web with caching needs                   | Mobile aggregating many entities         |

## Migration cost

Going REST-to-GraphQL is rarely worth it whole. Going GraphQL-to-REST happens when teams realize the operational cost of resolvers, DataLoader, and schema review outweighs the client benefit.

- Add a GraphQL layer on top of an existing REST API: wrap the REST endpoints with thin resolvers. Cheap if the REST API is well-designed; expensive if the REST API leaks database shape. Plan two to four engineer-weeks for a 50-endpoint API.
- Replace GraphQL with REST: design REST endpoints around the client views the GraphQL queries served. The frontend rewrites are the long pole.
- Avoid running both forever; pick one and deprecate the other on a clock.

## Recommendation

- Public API for third-party developers: REST with OpenAPI. Use cursor pagination, ETags, and idempotency keys.
- Internal microservices, server-to-server: REST or gRPC; GraphQL is the wrong layer.
- Mobile app aggregating many entities per screen: GraphQL with persisted queries and DataLoader.
- TypeScript monorepo where one client and one server share a repo: tRPC. See [[comparisons/monorepo-vs-polyrepo]] and [[comparisons/typescript-vs-javascript]].
- Real-time features (live cursors, presence, notifications): WebSockets or Server-Sent Events plus REST for the rest of the surface.
- Federated graph across five-plus teams: GraphQL with Apollo Federation.

## Related

- [[backend/fastapi]]
- [[comparisons/typescript-vs-javascript]]
- [[comparisons/monorepo-vs-polyrepo]]
- [[frontend/nextjs]]
- [[coding/typescript]]
