GraphQL vs REST: When to Use Each (A Pragmatic Guide)
Backend Developer | Golang & Python I enjoy building reliable APIs, distributed systems, and automation tools. Writing here about backend engineering, system design, and real-world dev experiences.
Introduction
I've built APIs both ways. REST APIs that worked beautifully. GraphQL APIs that were a nightmare. And vice versa.
The "GraphQL vs REST" debate is exhausting. Everyone has strong opinions. GraphQL fanboys say REST is dead. REST purists say GraphQL is overengineered.
Here's the truth: both have their place. I've shipped production systems with both. Some decisions were great. Some I regret.
In this post, I'll show you when to use each, based on real experience—not hype.
The Real Difference
REST in 30 Seconds
Multiple endpoints, each returns fixed data structure:
GET /users/123 → User data
GET /users/123/posts → User's posts
GET /posts/456 → Specific post
GET /posts/456/comments → Post's comments
You get what the server decides to send. Need more data? Make another request.
GraphQL in 30 Seconds
One endpoint, you specify exactly what you want:
query {
user(id: 123) {
name
email
posts {
title
comments {
text
author { name }
}
}
}
}
You get exactly what you asked for. Nothing more, nothing less.
When REST Wins
1. Simple CRUD Operations
If you're building a basic API with standard create/read/update/delete:
POST /users → Create user
GET /users/123 → Get user
PUT /users/123 → Update user
DELETE /users/123 → Delete user
REST is perfect. It's simple, standardized, everyone understands it.
Don't overthink it. If your API fits REST naturally, use REST.
2. File Uploads/Downloads
REST handles binary data naturally:
POST /users/123/avatar
Content-Type: multipart/form-data
GraphQL with file uploads? Possible, but awkward. You'll add libraries, workarounds, complexity.
Use REST for files. Save yourself the headache.
3. Caching is Critical
HTTP caching just works with REST:
GET /users/123
Cache-Control: max-age=3600
ETag: "abc123"
CDNs, browsers, proxies—everything caches GET requests automatically.
GraphQL? Every request is a POST to the same endpoint. Standard HTTP caching doesn't work.
Yes, you can implement caching in GraphQL. But it's extra work.
If caching matters, REST is easier.
4. Public APIs
Building an API for third-party developers?
REST wins because:
Simpler to understand
Works with standard HTTP tools (curl, Postman)
Better documentation tools (Swagger/OpenAPI)
Developers already know it
GraphQL has a learning curve. Not everyone wants to learn your schema.
5. Microservices Internal Communication
Service-to-service calls in a microservices architecture?
REST or gRPC. Not GraphQL.
Why?
Simple request-response
No need for flexible queries
Performance matters (gRPC is faster)
You control both sides of the API
When GraphQL Wins
1. Mobile Apps with Limited Bandwidth
Mobile apps need different data at different times. With REST:
GET /users/123 → Full user (waste bandwidth)
GET /users/123/posts → All posts (only need 5)
GET /posts/X/comments → Repeat for each post
Result: Multiple round trips, over-fetching, under-fetching.
With GraphQL:
query {
user(id: 123) {
name
avatar
posts(limit: 5) {
title
excerpt
}
}
}
One request. Exactly what you need. Huge win for mobile.
Mobile app with complex data needs? GraphQL.
2. Rapidly Evolving Frontend
Your frontend team needs different data every sprint. With REST:
// Week 1: Need user name
GET /users/123 → { name, email, created_at, ... }
// Week 2: Need user + posts
GET /users/123
GET /users/123/posts
// Week 3: Need user + posts + follower count
Backend: *creates new endpoint*
GET /users/123/profile
You keep adding endpoints. API becomes a mess.
With GraphQL:
// Week 1
query { user(id: 123) { name } }
// Week 2
query { user(id: 123) { name, posts { title } } }
// Week 3
query { user(id: 123) { name, posts { title }, followerCount } }
Frontend evolves independently. Backend adds fields, doesn't break existing queries.
Frontend changes fast? GraphQL gives flexibility.
3. Complex, Nested Data Requirements
Your UI shows deeply nested data:
User
├─ Posts
│ ├─ Comments
│ │ └─ Author
│ └─ Likes
└─ Followers
└─ Posts
With REST, you're making 10+ requests or creating bloated endpoints.
With GraphQL, one query gets it all:
query {
user(id: 123) {
posts {
comments { author { name } }
likes { count }
}
followers { posts { title } }
}
}
Complex nested data? GraphQL shines.
4. Multiple Client Types
You have:
Web app (needs full data)
Mobile app (needs minimal data)
Smartwatch app (needs even less)
With REST:
Create separate endpoints for each? Messy.
Return everything, let clients filter? Wasteful.
With GraphQL: Each client queries exactly what it needs. One API serves all.
Multiple client types with different needs? GraphQL.
5. Developer Productivity (with Good Tooling)
GraphQL tooling is incredible:
GraphiQL/Playground: Interactive API explorer
Schema introspection: Self-documenting API
Type safety: Auto-generated types for TypeScript/Go
Real-time updates: Built-in subscription support
This boosts developer productivity—if you invest in the tooling.
The Ugly Truth About GraphQL
GraphQL isn't all sunshine. Here's what people don't tell you:
Problem 1: N+1 Query Problem
query {
users {
posts {
author { name }
}
}
}
Naive implementation:
Fetch all users (1 query)
For each user, fetch posts (N queries)
For each post, fetch author (N² queries)
Your database dies.
Solution: DataLoader pattern (batching). But it's extra code.
Problem 2: Complex Authorization
REST:
func GetUser(w http.ResponseWriter, r *http.Request) {
if !canAccessUser(userID) {
return http.StatusForbidden
}
// Return user
}
GraphQL: Authorization happens at the field level. Users can query anything. You need field-level permission checks.
query {
user(id: 123) {
name # Public
email # Owner only
socialSecurity # Super admin only
}
}
Every field needs auth logic. It's complex.
Problem 3: Rate Limiting Hell
REST: Easy to rate limit by endpoint.
GraphQL: Every request is POST /graphql. How do you rate limit?
# Simple query
query { user(id: 123) { name } }
# Expensive query
query {
users {
posts {
comments {
author { posts { comments { ... } } }
}
}
}
}
Both hit the same endpoint. How do you limit the expensive one?
You need query complexity analysis. More code.
Problem 4: Caching Complexity
REST caching: Built into HTTP.
GraphQL caching: You implement it yourself.
Options:
Persisted queries (complex setup)
Apollo Client cache (learning curve)
Custom Redis caching (more code)
It's doable, but not free.
My Decision Framework
Here's how I decide:
Use REST When:
✓ Simple CRUD operations ✓ Public API for third parties ✓ File uploads/downloads ✓ HTTP caching is critical ✓ Team is small or new to GraphQL ✓ Service-to-service communication ✓ You need it done fast
Use GraphQL When:
✓ Mobile app with bandwidth concerns ✓ Complex, nested data requirements ✓ Multiple clients with different data needs ✓ Rapid frontend iteration ✓ Team has GraphQL experience ✓ You can invest in proper tooling ✓ Developer experience is a priority
Red Flags for GraphQL:
🚩 Team has never used it 🚩 Simple API (under 10 endpoints) 🚩 No time for proper setup 🚩 Performance is critical (GraphQL adds overhead) 🚩 Public API for unknown clients
Hybrid Approach (What I Actually Do)
Controversial take: You can use both.
Real example from my system:
REST for:
Authentication:
POST /auth/loginFile uploads:
POST /users/avatarHealth checks:
GET /healthWebhooks:
POST /webhooks/stripe
GraphQL for:
Dashboard data queries
Complex reporting
Mobile app data fetching
The endpoints live side by side. It works.
Don't be dogmatic. Use the right tool for each job.
Performance Reality Check
GraphQL isn't automatically slower or faster than REST. It depends on implementation.
Bad GraphQL: N+1 queries, no caching, no batching → Slow
Good GraphQL: DataLoader, query optimization, caching → Fast
Bad REST: 20 endpoints for one page, over-fetching → Slow
Good REST: Optimized queries, proper caching → Fast
Performance is about implementation, not technology choice.
Migration Strategy
REST → GraphQL
Don't rewrite everything. Incrementally add GraphQL:
Add GraphQL endpoint alongside REST
Implement new features in GraphQL
Gradually migrate high-traffic endpoints
Keep REST for simple operations
GraphQL → REST
Yes, this happens. I've seen it.
When?
Team couldn't manage complexity
Performance issues couldn't be solved
Caching requirements were critical
No shame in switching back if it's not working.
Tools & Libraries
GraphQL in Go:
gqlgen: Code generation from schema (my choice)
graphql-go: Reflection-based
99designs/gqlgen: Production-ready, type-safe
REST in Go:
gorilla/mux: Full-featured router
chi: Lightweight, composable
gin: Fast, batteries-included
echo: High performance
Both ecosystems are mature. Tooling isn't the deciding factor.
Common Mistakes
Mistake 1: GraphQL for Everything
"We're modern developers, we use GraphQL!"
Then you implement file uploads in GraphQL. And webhooks. And server-sent events.
Stop. Use REST for these. GraphQL isn't a religion.
Mistake 2: Multiple REST Endpoints for Same Data
GET /users/123/profile
GET /users/123/dashboard
GET /users/123/settings
GET /users/123/full
Different endpoints returning different subsets of user data.
This screams for GraphQL. Let clients specify what they need.
Mistake 3: No DataLoader in GraphQL
You implement GraphQL without solving N+1 queries.
Your database gets hammered. You blame GraphQL.
Not GraphQL's fault. Implement DataLoader or batching.
Mistake 4: Over-Engineering REST
You add versioning (/v1/, /v2/), custom query languages, complex filtering...
You're reinventing GraphQL badly.
Just use GraphQL if you need that flexibility.
The Bottom Line
There is no winner.
REST isn't dead. GraphQL isn't a silver bullet.
Building a simple CRUD API? REST.
Building a complex dashboard? GraphQL.
Building Stripe's API? REST.
Building GitHub's API? GraphQL (they use both, actually).
Stop following trends. Make practical decisions.
Questions? Drop a comment!