The API Paradigm Debate
REST has been the dominant API paradigm for over a decade, and GraphQL has emerged as a powerful alternative. Understanding when to use each — and when to combine them — is critical for modern API design. This is not about picking a winner; it is about choosing the right tool for the job.
Side-by-Side Comparison
| Aspect | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple endpoints (/users, /posts) | Single endpoint (/graphql) |
| Data fetching | Server decides response shape | Client decides response shape |
| Over-fetching | Common — endpoint returns all fields | Eliminated — client requests exact fields |
| Under-fetching | Common — requires multiple requests | Eliminated — fetch related data in one query |
| Versioning | URL versioning (/api/v1, /api/v2) | Schema evolution — no versioning needed |
| Caching | HTTP caching built-in (ETags, Cache-Control) | Requires client-side caching (Apollo Cache) |
| Type system | OpenAPI/Swagger (optional) | Built-in, mandatory schema |
| Real-time | Requires WebSocket or SSE separately | Subscriptions are part of the spec |
| File uploads | Native multipart support | Requires special handling |
The Over-Fetching Problem
Over-fetching occurs when an API returns more data than the client needs. This wastes bandwidth and processing time, especially on mobile devices:
// REST: Fetch user profile — returns ALL fields
// GET /api/users/42
{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
"bio": "Long bio text...",
"avatarUrl": "...",
"phone": "+1-555-0123",
"address": { "street": "...", "city": "...", "country": "..." },
"preferences": { ... },
"createdAt": "2024-01-01",
"updatedAt": "2025-03-15",
"lastLoginAt": "2025-04-05"
// 20+ more fields the mobile app doesn't need
}
# GraphQL: Fetch only what the mobile header needs
query MobileUserHeader {
user(id: "42") {
name
avatarUrl
}
}
# Response: { "data": { "user": { "name": "Alice", "avatarUrl": "..." } } }
The Under-Fetching Problem
Under-fetching requires multiple round-trips to gather related data. This is particularly painful on slow mobile connections:
// REST: Building a blog post page requires 3+ requests
// Request 1: GET /api/posts/1
const post = await fetch('/api/posts/1').then(r => r.json());
// Request 2: GET /api/users/42 (the author)
const author = await fetch(`/api/users/${post.authorId}`).then(r => r.json());
// Request 3: GET /api/posts/1/comments
const comments = await fetch('/api/posts/1/comments').then(r => r.json());
// Request 4: GET /api/users/5, GET /api/users/8 (comment authors)
const commentAuthors = await Promise.all(
comments.map(c => fetch(`/api/users/${c.authorId}`).then(r => r.json()))
);
// Total: 4+ HTTP round-trips, high latency on mobile
# GraphQL: One request gets everything
query BlogPostPage {
post(id: "1") {
title
body
publishedAt
author {
name
avatarUrl
bio
}
comments {
text
createdAt
author {
name
avatarUrl
}
}
tags {
name
slug
}
}
}
# Total: 1 HTTP request
Where REST Excels
REST Strengths
- HTTP Caching: REST APIs benefit from the entire HTTP caching infrastructure — CDNs, browser cache, ETags, and Cache-Control headers work out of the box
- Simplicity: For simple CRUD APIs with few relationships, REST is straightforward and well-understood
- File Uploads: Multipart form data is natively supported in REST; GraphQL requires workarounds
- Webhooks: REST endpoints are natural targets for third-party webhook integrations
- Browser-friendly: REST APIs can be tested directly in a browser address bar
- Maturity: Decades of tooling, middleware, and best practices
Where GraphQL Excels
GraphQL Strengths
- Complex Data Requirements: When views need data from multiple related resources, GraphQL eliminates the waterfall of REST requests
- Multiple Client Platforms: Mobile, web, and TV apps can each request exactly the data they need without custom endpoints
- Rapid Frontend Iteration: Frontend developers can change data requirements without waiting for backend changes
- Strong Typing: The schema is the contract, enabling code generation, validation, and self-documenting APIs
- Real-time: Subscriptions are a first-class feature, not bolted on
- Developer Experience: GraphiQL, introspection, and auto-generated docs make exploration easy
Hybrid Approach
Many production systems use both GraphQL and REST together. This is often the most pragmatic approach:
// Common hybrid architecture
// GraphQL for complex reads
// POST /graphql
query DashboardData {
currentUser { name role notifications { ... } }
recentPosts { ... }
analytics { ... }
}
// REST for simple operations
// POST /api/upload (file upload)
// GET /api/health (health check)
// POST /api/webhooks/stripe (webhook receiver)
// GET /api/export/csv (file download)
Decision Framework
- Choose REST when: Simple CRUD, heavy caching needs, file operations, third-party integrations, or your team is already experienced with REST
- Choose GraphQL when: Complex data relationships, multiple client platforms, rapid frontend iteration, real-time requirements, or microservice aggregation
- Choose both when: Most real-world applications benefit from using GraphQL for complex reads and REST for simple operations, uploads, and webhooks