GraphQL is a query language for APIs that lets clients request exactly the data they need. It’s been adopted by major companies and has a strong ecosystem. It also adds complexity that isn’t always justified.
Where GraphQL Shines
Complex, Connected Data
When your data model is a graph of relationships and different clients need different views:
query {
user(id: "42") {
name
posts(first: 5) {
title
comments(first: 3) {
body
author { name }
}
}
followers { totalCount }
}
}
One request, exactly the data the client needs. No over-fetching, no under-fetching, no waterfall of REST calls.
Multiple Client Types
A mobile app needs a subset of what the web app needs. With REST, you either over-fetch on mobile or maintain separate endpoints. With GraphQL, each client queries for what it needs.
Rapid Frontend Development
Frontend developers can iterate on data requirements without waiting for backend changes. Add a field to a query, not a ticket to the backend team’s backlog.
Where GraphQL Hurts
Simple CRUD APIs
If your API is mostly create/read/update/delete operations on independent resources, REST is simpler. GraphQL adds schema definition, resolver implementation, and query parsing overhead for no benefit.
Caching
REST gets HTTP caching for free. GET /api/users/42 is cacheable at every layer (browser, CDN, reverse proxy). GraphQL queries are POST requests with unique bodies — standard HTTP caching doesn’t apply. You need application-level caching (Apollo’s normalized cache, persisted queries, etc.).
Security
GraphQL’s flexibility is a security surface:
- Deeply nested queries can be denial-of-service attacks
- Introspection exposes your entire schema
- Field-level authorization is complex to implement correctly
# This could generate millions of database queries
query {
users {
posts {
comments {
author {
posts {
comments { ... }
}
}
}
}
}
}
You need query depth limiting, complexity analysis, and rate limiting.
N+1 Queries
The naive resolver implementation hits the database once per field, per object:
const resolvers = {
User: {
posts: (user) => db.posts.findByUserId(user.id), // Called N times
},
};
DataLoader (batching + caching) mitigates this but adds complexity.
The Decision Framework
Use GraphQL when:
- Multiple clients need different data shapes
- Your data model is highly connected
- Frontend velocity is more important than backend simplicity
Use REST when:
- Your API is CRUD-centric
- You need aggressive HTTP caching
- Your team is small and maintaining a schema is overhead
- Your API is public-facing (REST is more universally understood)
GraphQL is a tool, not an upgrade. Use it when the problem fits.