GraphQL Rate Limiting — Protect Your API from Abuse and Overuse
In this tutorial, you will learn about GraphQL Rate Limiting. We cover key concepts, practical examples, and best practices to help you master this topic.
GraphQL rate limiting protects your API from abusive queries and excessive resource consumption by restricting the number, depth, or complexity of operations a client can perform within a time window.
What You'll Learn
- Why REST rate limiting doesn't work for GraphQL
- Query complexity analysis and depth limiting
- Per-user and per-IP rate limiting
- Implementing rate limits in Apollo Server
- Using Sliding Window and token bucket algorithms
Why It Matters
Unlike REST where each endpoint has a predictable cost, a single GraphQL query can request hundreds of fields and deeply nested relations. Without rate limiting, a malicious or buggy client could trigger an expensive query that degrades the entire API. DodaTech's Durga Antivirus Pro uses cost-based rate limiting, assigning weights to fields and rejecting queries that exceed a per-user daily budget.
Real-World Use
A mobile app queries device status every 30 seconds. A bug causes it to send the same query every second. Rate limiting catches the excessive traffic and returns 429 Too Many Requests, preventing the database from being overloaded while the bug is fixed.
flowchart TB
A["Client Query"] --> B["Rate Limiter"]
B --> C{Check Budget}
C -->|Within Limit| D["Execute Query"]
C -->|Exceeded| E["Return 429 Error"]
D --> F["Decrement Budget"]
subgraph Budget Store
G["Redis\nUser: quota_remaining\nWindow: 1 hour"]
end
B <--> G
style B fill:#fef3c7,stroke:#d97706
style E fill:#fecaca,stroke:#dc2626
Code Examples
Example 1: Query Depth Limiting
const depthLimit = require('graphql-depth-limit');
const { ApolloServer } = require('apollo-server');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(5)],
});
// Query rejected: depth exceeds 5
// query { devices { user { devices { user { devices { name } } } } } }
Example 2: Cost-Based Rate Limiting
const { ApolloServer } = require('apollo-server');
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const complexityRule = createComplexityLimitRule(1000, {
onCost: cost => console.log(`Query cost: ${cost}`),
formatErrorMessage: cost =>
`Query too complex (cost ${cost}/1000). Simplify the query.`,
});
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [complexityRule],
});
Example 3: Per-User Sliding Window Rate Limiter
const redis = require('redis');
const client = redis.createClient();
async function checkRateLimit(userId, maxQueries, windowSec) {
const key = `rate:${userId}`;
const now = Date.now();
const windowStart = now - windowSec * 1000;
await client.zRemRangeByScore(key, 0, windowStart);
const count = await client.zCard(key);
if (count >= maxQueries) {
const ttl = await client.zRangeByScore(key, 0, now, { LIMIT: [0, 1] });
throw new Error('Rate limit exceeded');
}
await client.zAdd(key, { score: now, value: `${now}` });
await client.expire(key, windowSec);
}
const resolvers = {
Query: {
devices: async (_, args, context) => {
await checkRateLimit(context.user.id, 100, 60);
return context.db.devices.find().toArray();
},
},
};
Common Mistakes
- Applying REST-style IP-based rate limiting — a single IP behind NAT represents many users. Rate limit by user ID or API key instead.
- Only limiting by query count — a single query requesting 10,000 records costs more than 10 queries requesting 10 each. Use cost analysis.
- Ignoring subscription rate limits — subscriptions can send messages continuously. Limit the subscription rate and disconnect abusive clients.
- Not communicating limits to clients — return proper 429 status with Retry-After headers and remaining quota in response metadata.
- Using fixed Windows instead of sliding windows — fixed windows (reset at midnight) can cause traffic spikes at the boundary.
Practice Questions
- Why is query depth limiting insufficient for protection?
- How does cost-based analysis assign weights to fields?
- What is the difference between per-IP and per-user rate limiting?
- How do sliding windows differ from fixed windows?
- How should rate-limited clients handle 429 responses?
Challenge: Build a rate limiter plugin for Apollo Server that uses Redis to track per-user query complexity costs across a sliding 1-hour window, with different limits for authenticated vs anonymous users.
Mini Project
Create a GraphQL API protected by a three-layer rate limiter: query depth (max 7), query cost (max 500 points), and per-user query count (1000/hour). Include proper error responses with Retry-After headers and remaining budget metadata.
FAQ
What's Next
Learn about query cost analysis and field weighting
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro