GraphQL: When REST Isn't Enough
REST works beautifully when your clients have simple, predictable data needs. But what happens when a mobile app needs a tiny slice of data, a web dashboard needs deeply nested objects, and an admin panel needs everything? You end up with either over-fetching (sending too much data) or under-fetching (requiring multiple round trips). GraphQL solves this by letting clients ask for exactly what they need.
The Problem GraphQL Solves
Over-fetching & Under-fetching
Over-fetching: GET /users/42 returns 30 fields when you only need name and avatar. Wasted bandwidth, especially on mobile.
Under-fetching: To show a user's profile with their latest orders and each order's products, you need:
GET /users/42→ get userGET /users/42/orders→ get ordersGET /orders/7/products,GET /orders/8/products... → get products per order
That's 4+ requests and a waterfall of latency. GraphQL does it in one.
Schema Definition
Types, Queries, Mutations, Subscriptions
GraphQL APIs are defined by a schema — a strongly typed contract between client and server:
type User {
id: ID!
name: String!
email: String!
orders: [Order!]!
}
type Order {
id: ID!
total: Float!
status: OrderStatus!
products: [Product!]!
}
type Query {
user(id: ID!): User
users(limit: Int, after: String): UserConnection
}
type Mutation {
createOrder(input: CreateOrderInput!): Order!
updateUser(id: ID!, input: UpdateUserInput!): User!
}
type Subscription {
orderStatusChanged(orderId: ID!): Order!
}
Queries read data, Mutations write data, and Subscriptions push real-time updates over WebSockets.
The N+1 Problem
Naive Resolvers Are Deadly
Consider fetching 50 users with their orders. A naive implementation:
- 1 query to get 50 users
- 50 queries to get each user's orders (one per user)
That's 51 database queries for one GraphQL request! The solution: DataLoader.
// DataLoader batches individual loads into one query
const orderLoader = new DataLoader(async (userIds) => {
// One query: SELECT * FROM orders WHERE user_id IN (...)
const orders = await db.orders.findByUserIds(userIds);
// Return orders grouped by user_id in the same order as input
return userIds.map(id => orders.filter(o => o.userId === id));
});
DataLoader collects all .load(userId) calls within a single tick, batches them into one DB query, then distributes results back. 51 queries → 2 queries.
When to Use What
| Dimension | REST | GraphQL |
|---|---|---|
| Data fetching | Fixed endpoints, fixed shapes | Flexible queries, client-defined shapes |
| Caching | HTTP caching works perfectly (GET + URL = cache key) | Harder — all requests are POST to /graphql |
| File uploads | Native multipart support | Requires workarounds (multipart spec extension) |
| Multiple clients | Often need separate BFF endpoints | Each client queries exactly what it needs |
| Learning curve | Low — HTTP basics suffice | Medium — schema, resolvers, DataLoader |
| Tooling | Mature — curl, Postman, any HTTP client | Great — GraphiQL, codegen, type safety |
| Best for | Simple CRUD, public APIs, microservices | Complex UIs, mobile apps, diverse clients |
Security Concerns
GraphQL Gives Clients Too Much Power?
Without guards, a malicious client can craft deeply nested or absurdly expensive queries:
# Malicious query — exponential nesting
{ user { friends { friends { friends { friends { ... } } } } } }
Defenses:
- Query depth limiting: Reject queries deeper than N levels
- Complexity analysis: Assign cost to each field, reject queries exceeding a budget
- Persisted queries: Clients can only execute pre-approved query hashes — no arbitrary queries in production
- Timeout & rate limiting: Kill slow resolvers, limit requests per minute
🌍 Facebook: Where GraphQL Was Born
Facebook created GraphQL in 2012 for their mobile app rewrite. The problem: their REST API returned massive payloads that mobile devices on 2G/3G couldn't handle efficiently. Results:
- 50% reduction in data transferred to mobile apps
- Eliminated hundreds of one-off REST endpoints
- Mobile and web teams could iterate independently — no backend changes needed for new UI features
🌍 Shopify's Storefront API
Shopify serves thousands of unique storefronts — each needs different data shapes. Their GraphQL Storefront API lets each merchant's custom frontend fetch exactly the product data, collections, and checkout info it needs without Shopify maintaining thousands of endpoint variations.
Interactive: Write a GraphQL Query
🎮 Query Builder
Write a GraphQL query to fetch the requested data. The response shape will match your query!