REST vs GraphQL
REST and GraphQL are two different philosophies for API design. REST is the traditional approach, focused on resources and HTTP methods. GraphQL is newer, focused on giving clients exactly the data they request. Each has genuine tradeoffs.
REST: Resource-Centric Design
REST organises around resources. Users, posts, comments are resources. Each resource has a URL. GET /api/users gets users. GET /api/users/1 gets user 1. POST /api/users creates a user. DELETE /api/users/1 deletes user 1.
REST is simple and universally understood. Every developer knows what GET means. HTTP verbs map naturally to operations. REST APIs are easy to build, easy to test (just use curl), easy to cache (HTTP caching works naturally).
GraphQL: Query Language for APIs
GraphQL is a query language. The client specifies exactly what fields it needs. Instead of GET /api/users/1 returning all fields, the client sends a query specifying name and email, and gets back exactly those fields.
The benefit: the client avoids over-fetching (getting fields it doesn't need) and under-fetching (needing to make multiple requests). A single query can fetch users, their posts, and comments on those posts—all in one request, with exactly the fields needed.
The Over-Fetching Problem
GET /api/users/1 returns all user fields: id, name, email, phone, address, preferences, subscription_status, etc. Maybe the client only needs name and email. REST sends unnecessary data.
GraphQL solves this. Query: "Get user 1, return name and email." Response: only those fields. No waste.
You can also solve this in REST with query parameters: GET /api/users/1?fields=name,email. But this requires custom implementation. GraphQL has it built in.
The Under-Fetching Problem
A user has many posts. GET /api/users/1 returns the user, but not posts. To get posts, you need GET /api/users/1/posts. That's two requests. If posts have comments, now you need GET /api/posts/123/comments. Multiple round trips.
GraphQL handles this with a single query: return user with nested posts with nested comments. One request, all data.
When REST Wins
REST is the right default. It's simpler, standard, and sufficient for most applications. Simple CRUD operations, predictable access patterns, straightforward client applications—REST handles these well.
REST is easier to cache. HTTP caching works naturally. API gateways and CDNs understand HTTP caching. GraphQL requires custom caching logic.
REST is easier to monitor and debug. You can inspect requests with curl. HTTP status codes have meaning. Errors are clear.
When GraphQL Earns Its Complexity
GraphQL wins when you have multiple clients with different data needs. A web app, mobile app, and third-party developers all consume your API. Each has different data requirements. A single REST endpoint can't satisfy all.
GraphQL wins when bandwidth matters. Mobile applications over slow networks benefit from only fetching needed fields. GraphQL reduces over-fetching and network usage.
GraphQL wins when you want strong typing and documentation built into the API. GraphQL schemas are self-documenting. Tools auto-generate type definitions and documentation.
GraphQL's Complexity Costs
GraphQL requires server infrastructure—query parsing, validation, execution. Simple queries might be slower than REST due to overhead. Query building is more complex than HTTP requests.
Caching is harder. Every GraphQL query is a POST to a single endpoint. HTTP caching doesn't apply. You need custom caching logic.
N+1 query problems are easier to create in GraphQL. A query asking for users and their posts might execute one query for users, then one query per user for posts. This is N+1. REST has the same problem, but GraphQL makes it easier to accidentally create.
Learning curve. GraphQL requires learning a query language and its tools. REST is familiar to every developer.
tRPC: A TypeScript Middle Ground
tRPC is an alternative for full-stack TypeScript applications. Instead of building a REST or GraphQL API, you define procedures in your back-end and call them from the front-end as if they're local functions. Types are shared, so the front-end knows exactly what the back-end can do.
tRPC is excellent for monolithic applications where front-end and back-end are in the same codebase. It's not suitable for public APIs or multi-language ecosystems.
The Decision Matrix
Small application with one client: REST. Simple, sufficient.
Multiple clients with different data needs: GraphQL or REST with query parameters.
Full-stack TypeScript monolith: tRPC.
Public API with external developers: REST (most ecosystems understand it better) or GraphQL (if your data is complex).
| Aspect | REST | GraphQL | tRPC |
|---|---|---|---|
| Learning curve | Very easy | Moderate | Easy (TypeScript only) |
| Over-fetching | Possible, needs workarounds | Prevented by design | N/A |
| Under-fetching | Possible, multiple requests | Single query | N/A |
| Caching | HTTP caching built-in | Custom caching needed | N/A |
| Debugging | Easy with curl | GraphQL tools needed | IDE integration |
| Complexity | Low | High | Medium |
| Public API | Excellent | Good | Not suitable |
| Internal only | Good | Good | Excellent |
| Multiple clients | Requires endpoints per client | Single endpoint for all | Single endpoint |