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 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, andVaryheaders 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.
- Idempotency is well-understood at the verb level:
GET,PUT,DELETEare idempotent;POSTis 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 monorepo-vs-polyrepo and 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.