GraphQL vs REST: When to Choose What
GraphQL isn't a replacement for REST—it's an alternative with different trade-offs. After building production systems with both, here's how to decide.
Quick Comparison
| Aspect | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple | Single |
| Data fetching | Fixed response | Client specifies |
| Over-fetching | Common | Eliminated |
| Learning curve | Lower | Higher |
| Caching | Simple (HTTP) | Complex |
| File uploads | Easy | Tricky |
REST in Action
// GET /api/users/123
{
"id": 123,
"name": "Amit",
"email": "[email protected]",
"address": { ... },
"orders": [...] // May include huge order history
}
// Need user's posts? Another request
// GET /api/users/123/posts
GraphQL in Action
query {
user(id: "123") {
name
email
posts(first: 5) {
title
createdAt
}
}
}
# Response: Exactly what you asked for
{
"data": {
"user": {
"name": "Amit",
"email": "[email protected]",
"posts": [...]
}
}
}
When to Choose REST
✅ Simple CRUD applications
- Blog with basic operations
- Standard admin panels
✅ Team unfamiliar with GraphQL
- Learning curve is real
- REST is well-understood
✅ Heavy caching requirements
- HTTP caching is simpler
- CDN integration is easier
✅ File uploads
- REST handles multipart easily
- GraphQL needs workarounds
✅ Public APIs
- REST is more familiar to consumers
- Better documentation tools
When to Choose GraphQL
✅ Mobile applications
- Minimize data transfer
- Single request for complex screens
✅ Multiple client types
- Web needs different data than mobile
- Each client fetches what it needs
✅ Rapidly changing frontend
- No backend changes for new fields
- Frontend team independence
✅ Complex nested relationships
- Avoid waterfall requests
- Get everything in one query
GraphQL Gotchas
N+1 Query Problem
query {
posts { # 1 query for posts
author { # N queries for authors!
name
}
}
}
Solution: Use DataLoader
const authorLoader = new DataLoader(async (authorIds) => {
const authors = await db.users.findMany({
where: { id: { in: authorIds } }
});
return authorIds.map(id => authors.find(a => a.id === id));
});
Query Complexity
Clients can write expensive queries:
query {
users {
posts {
comments {
author {
posts {
comments { ... }
}
}
}
}
}
}
Solution: Query depth limiting, complexity analysis
Caching
REST: Browser/CDN caches by URL GraphQL: All requests are POST to same URL
Solution: Persisted queries, Apollo caching
Implementation Example
// GraphQL with Apollo Server
const { ApolloServer, gql } = require('apollo-server-express');
const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
user(id: ID!): User
posts: [Post!]!
}
`;
const resolvers = {
Query: {
user: (_, { id }) => db.users.findUnique({ where: { id } }),
posts: () => db.posts.findMany()
},
User: {
posts: (user) => db.posts.findMany({ where: { authorId: user.id } })
}
};
const server = new ApolloServer({ typeDefs, resolvers });
My Recommendation
- Default to REST for most projects
- Consider GraphQL once you hit REST limitations
- Never choose based on hype - choose based on needs
Both are tools. Pick the right one for your job.