Modern web applications receive thousands of requests every day. Without proper protection, your API can become vulnerable to abuse, brute-force attacks, excessive traffic, and even server crashes.
This is where a Rate Limiter in Node.js becomes extremely important.
A rate limiter controls how many requests a user can make within a specific period. It helps protect your application, improve performance, and ensure fair usage of your APIs.
In this guide, you will learn:
- What a rate limiter is
- Why it is important
- How to implement a rate limiter in Node.js
- How to use it with Express
- How to use it with Apollo Server GraphQL APIs
- Best practices
- Common mistakes to avoid
By the end of this article, you’ll be able to implement a production-ready Rate Limiter in Node.js.

What Is a Rate Limiter?
A rate limiter is a mechanism that restricts the number of requests a client can make within a specific time window.
For example:
| Time Window | Maximum Requests |
|---|---|
| 1 minute | 100 |
| 1 hour | 1000 |
| 1 day | 10000 |
If the limit is exceeded, the server returns:
429 Too Many RequestsWhy Use a Rate Limiter in Node.js?
Without rate limiting, your API is vulnerable to:
DDoS Attacks
Attackers can send millions of requests.
Brute Force Attacks
Login endpoints are common targets.
Resource Exhaustion
Expensive database queries can overload your server.
Third-Party API Limits
If your application calls external services, excessive requests may exceed vendor limits.
Fair Usage
Prevents a single user from consuming all resources.
How Rate Limiting Works
A rate limiter tracks requests from a user based on:
- IP Address
- User ID
- API Key
- Session ID
Example:
User IP: 192.168.1.100
Allowed:
100 requests / minuteRequest count:
1
2
3
...
100Request 101:
429 Too Many RequestsTypes of Rate Limiting Algorithms
Fixed Window
Simple implementation.
Example:
100 requests per minuteCounter resets every minute.
Pros
- Easy
Cons
- Traffic spikes at boundary times
Sliding Window
Tracks requests over a rolling period.
Example:
Last 60 secondsMore accurate than Fixed Window.
Token Bucket
Each user receives tokens.
Example:
100 tokensEach request consumes a token.
Tokens refill over time.
Best For
- APIs
- Microservices
Leaky Bucket
Requests enter a queue and are processed at a fixed rate.
Best For
- Traffic smoothing
Implementing Rate Limiter in Node.js
Let’s build one using Express.
Install packages:
npm install express express-rate-limitExpress Rate Limiter Example
Create:
app.jsCode language: CSS (css)import express from "express";
import rateLimit from "express-rate-limit";
const app = express();
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: {
error: "Too many requests. Try again later."
}
});
app.use(limiter);
app.get("/", (req, res) => {
res.send("API Running");
});
app.listen(3000);Code language: JavaScript (javascript)This configuration allows:
100 requests every 15 minutesApplying Rate Limiting Only to Login Routes
Login endpoints need stricter limits.
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: "Too many login attempts"
});
app.use("/login", loginLimiter);Code language: PHP (php)Now users can only attempt login:
5 times every 15 minutesCustom Rate Limiter in Node.js
Sometimes you need more control.
Example:
const requestMap = new Map();
function customRateLimiter(req, res, next) {
const ip = req.ip;
const currentTime = Date.now();
if (!requestMap.has(ip)) {
requestMap.set(ip, {
count: 1,
startTime: currentTime
});
return next();
}
const user = requestMap.get(ip);
if (currentTime - user.startTime > 60000) {
user.count = 1;
user.startTime = currentTime;
return next();
}
user.count++;
if (user.count > 100) {
return res.status(429).json({
error: "Rate limit exceeded"
});
}
next();
}Code language: JavaScript (javascript)Apply middleware:
app.use(customRateLimiter);Code language: PHP (php)Rate Limiting in Apollo Server GraphQL
GraphQL APIs can also be abused.
Example Apollo Server:
import { ApolloServer } from "@apollo/server";Code language: JavaScript (javascript)Add middleware before GraphQL:
app.use(
"/graphql",
rateLimit({
windowMs: 60000,
max: 50
})
);Code language: PHP (php)Complete example:
app.use(
"/graphql",
rateLimit({
windowMs: 60000,
max: 50,
message: {
error: "GraphQL rate limit exceeded"
}
})
);Code language: JavaScript (javascript)This protects expensive GraphQL queries.
Using Redis for Distributed Rate Limiting
In production, multiple Node.js servers may be running.
Using memory-based storage won’t work.
Use Redis instead.
Install:
npm install redis
npm install rate-limit-redisExample:
import RedisStore from "rate-limit-redis";
import { createClient } from "redis";
const redisClient = createClient();
const limiter = rateLimit({
store: new RedisStore({
sendCommand: (...args) =>
redisClient.sendCommand(args)
}),
windowMs: 15 * 60 * 1000,
max: 100
});Code language: JavaScript (javascript)Benefits:
- Shared counters
- Works across multiple servers
- Production ready
Real-World Example: External API Rate Limit
Suppose your application calls an external API that allows:
40 requests per secondYou can build a queue.
Example:
class SimpleRateLimiter {
constructor(maxRequests, intervalMs) {
this.maxRequests = maxRequests;
this.intervalMs = intervalMs;
this.queue = [];
this.running = false;
}
async schedule(task) {
return new Promise((resolve, reject) => {
this.queue.push({
task,
resolve,
reject
});
this.run();
});
}
async run() {
if (this.running) return;
this.running = true;
while (this.queue.length) {
const batch = this.queue.splice(
0,
this.maxRequests
);
await Promise.all(
batch.map(async item => {
try {
const result = await item.task();
item.resolve(result);
} catch (error) {
item.reject(error);
}
})
);
await new Promise(resolve =>
setTimeout(resolve, this.intervalMs)
);
}
this.running = false;
}
}Code language: JavaScript (javascript)Usage:
const limiter = new SimpleRateLimiter(
40,
1000
);
await limiter.schedule(() =>
axios.get(url)
);Code language: JavaScript (javascript)This ensures:
Maximum 40 requests per secondBest Practices for Rate Limiter in Node.js
Use Different Limits
Different routes need different limits.
Example:
Login: 5/minute
Search: 100/minute
Public API: 1000/minuteCode language: PHP (php)Use Redis in Production
Memory-based storage resets on restart.
Redis provides persistence.
Return Helpful Messages
Bad:
{
"error": "Denied"
}Code language: JSON / JSON with Comments (json)Good:
{
"error": "Rate limit exceeded",
"retryAfter": 60
}Code language: JSON / JSON with Comments (json)Monitor Rate Limit Events
Log excessive requests.
Example:
logger.warn(
"Rate limit exceeded",
{
ip: req.ip
}
);Code language: CSS (css)Protect GraphQL Endpoints
GraphQL queries can be expensive.
Always add rate limiting.
Common Mistakes
Using Memory Store in Production
Bad:
const limiter = rateLimit();Code language: JavaScript (javascript)Problem:
Counters disappear after restartOne Limit for Every Route
Different endpoints require different limits.
Ignoring Internal APIs
Internal APIs can also be abused.
Apply limits everywhere.
Not Handling Proxy Servers
Behind:
- Nginx
- Azure Application Gateway
- AWS Load Balancer
Enable:
app.set("trust proxy", 1);Code language: JavaScript (javascript)Otherwise IP tracking may fail.
Not Returning HTTP 429
Always use:
429 Too Many Requestsfor rate-limit violations.
Performance Benefits of Rate Limiting
A properly implemented Rate Limiter in Node.js helps:
- Reduce server load
- Prevent abuse
- Improve response times
- Protect databases
- Protect third-party APIs
- Improve application stability
For high-traffic applications, rate limiting is not optional—it is essential.
Conclusion
Implementing a Rate Limiter in Node.js is one of the most important security and performance improvements you can make for your application. Whether you are building REST APIs with Express or GraphQL APIs with Apollo Server, rate limiting helps prevent abuse, protects backend resources, and improves reliability.
For small projects, express-rate-limit is an excellent choice. For production environments with multiple servers, combine rate limiting with Redis. If you need to respect third-party API quotas, a custom queue-based rate limiter can provide precise control over request throughput.
By following the techniques and best practices covered in this guide, you can build secure, scalable, and production-ready Node.js applications.