Introduction
In today’s API-driven world, preventing abuse and securing your endpoints is crucial—especially for public APIs. One powerful approach is rate limiting, which controls how many requests a user or client can make in a given time frame. While there are popular packages (like express-rate-limit
), building your own custom rate limiter gives you deeper understanding and flexibility for complex scenarios.
In this advanced, in-depth tutorial, you’ll learn how to build a robust, custom rate limiter middleware in Node.js (using Express), including:
- User-based and IP-based rate limiting
- Configurable burst windows (sliding window logic)
- Easy plug-in for APIs and routes
- Best practices for production use
If you’re building secure APIs, microservices, or want total control over request throttling, this post is for you.
1️⃣ Setting Up Your Express Project
Let’s start with a basic Express setup.
// index.js
const express = require('express');
const app = express();
app.use(express.json());
// Example endpoint (replace with your API routes)
app.get('/api', (req, res) => {
res.json({ message: 'API working!' });
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
Explanation:
- We import
express
, create an app, and addexpress.json()
middleware for JSON parsing. - The
/api
route is a simple placeholder—you can add any protected route later. - The server listens on port 3000.
2️⃣ Designing the Rate Limiter Middleware
Now, let's build the rate limiter as standalone middleware.
// rateLimiter.js
const rateLimitMap = new Map();
function rateLimiter(options) {
const {
windowMs = 60000, // Time window in ms (default: 1 min)
maxRequests = 10, // Max requests per window per user/IP
keyGenerator = (req) => req.ip // How to identify clients (default: IP)
} = options;
return (req, res, next) => {
const key = keyGenerator(req);
const now = Date.now();
let record = rateLimitMap.get(key);
if (!record) {
record = { count: 1, startTime: now };
} else {
if (now - record.startTime < windowMs) {
record.count += 1;
} else {
// Reset window
record = { count: 1, startTime: now };
}
}
rateLimitMap.set(key, record);
if (record.count > maxRequests) {
res.status(429).json({
error: 'Too many requests, please try again later.'
});
} else {
next();
}
};
}
module.exports = rateLimiter;
Explanation:
- We maintain a
rateLimitMap
to track request counts by a key (default: client IP). windowMs
: Duration of the rate window (default 1 minute).maxRequests
: Allowed requests per window.keyGenerator
: Lets you switch between IP, API key, user ID, etc.- When a request comes in:
- We check or create the client’s record.
- If still in the time window, increment the count; else, reset for a new window.
- If over limit, respond with
429 Too Many Requests
. - Otherwise, let the request proceed.
3️⃣ Plugging the Middleware into Express
Let’s add the middleware to your app.
// index.js (continued)
const rateLimiter = require('./rateLimiter');
// Apply to all API routes
app.use('/api', rateLimiter({
windowMs: 60000, // 1 minute
maxRequests: 5, // 5 requests per minute per IP
}));
app.get('/api', (req, res) => {
res.json({ message: 'API with rate limiting' });
});
Explanation:
- We import our
rateLimiter
. - Add it to
/api
routes with a 1-minute window and a strict 5-request limit for demo. - This ensures any endpoint under
/api
is protected.
4️⃣ Advanced: Using User Authentication for Rate Limiting
Sometimes, you want to rate limit per user rather than by IP (which can be shared or dynamic).
// index.js (continued)
// Fake user authentication for demo purposes
app.use((req, res, next) => {
// In real app: set req.user.id based on JWT/auth
req.user = { id: req.headers['x-demo-user'] || req.ip };
next();
});
// Apply rate limiter with custom keyGenerator (user-based)
app.use('/api/user', rateLimiter({
windowMs: 60000,
maxRequests: 10,
keyGenerator: (req) => req.user.id, // Now rate limit by user ID!
}));
app.get('/api/user', (req, res) => {
res.json({ message: `Hello User ${req.user.id}, you're being rate limited by user!` });
});
Explanation:
- We simulate authentication by assigning
req.user.id
(replace with your real auth logic). - The rate limiter’s
keyGenerator
now uses the user ID instead of IP. - This pattern lets you apply fine-grained limits per logged-in user.
5️⃣ Optional: Sliding Window (Burst-Friendly) Logic
For real-world APIs, fixed windows can cause “burstiness.” A more advanced sliding window logic smooths request allowance. Here’s a minimal example:
// rateLimiter.js (add this variant)
function slidingWindowRateLimiter(options) {
const {
windowMs = 60000,
maxRequests = 10,
keyGenerator = (req) => req.ip
} = options;
const requests = new Map();
return (req, res, next) => {
const key = keyGenerator(req);
const now = Date.now();
if (!requests.has(key)) requests.set(key, []);
// Filter out old timestamps
const timestamps = requests.get(key).filter(ts => now - ts < windowMs);
timestamps.push(now);
requests.set(key, timestamps);
if (timestamps.length > maxRequests) {
res.status(429).json({ error: 'Too many requests (sliding window)' });
} else {
next();
}
};
}
module.exports = { rateLimiter, slidingWindowRateLimiter };
Explanation:
- For each key, we keep an array of timestamps.
- On each request, remove any outside the current window, and check count.
- This logic allows “bursts” spread smoothly across the window, preventing harsh resets.
6️⃣ Testing and Debugging
You can test your API with tools like Postman, curl, or your favorite HTTP client:
curl http://localhost:3000/api
Try sending multiple requests quickly to hit the limit!
📝 Gentle Note to Readers
To see the full working rate limiter, simply combine all the snippets above—starting with the Express setup, then adding the middleware, and finally customizing as needed for your project!
🔥 Best Practices & Tips
- For distributed systems (multi-instance), consider external storage (Redis, Memcached) for rate limiting state.
- Customize
keyGenerator
for flexibility—API keys, users, sessions, or custom headers. - Always set proper HTTP headers (
Retry-After
) to inform clients when they can retry. - Clean up your in-memory maps periodically to avoid memory leaks.
- Monitor rate limiting metrics to fine-tune for real-world traffic.
💡 SEO & Marketing Tie-In
Implementing effective rate limiting directly improves API reliability, site speed, and user experience—all key signals for SEO. Search engines and users both love sites that stay up under heavy load. Plus, robust rate limiting protects you from abuse, helps maintain uptime, and demonstrates technical credibility to clients and employers.
🏁 Conclusion
Congratulations! You now know how to build a custom, flexible rate limiter middleware for Express—giving you more power than off-the-shelf solutions. This skill is crucial for API developers, SaaS teams, and anyone deploying services at scale.
What advanced middleware or security features should we cover next? Leave a comment, try the code, and explore our other backend & API tutorials.
🔗 Stay Connected with Tech Talker 360
Want to automate your rate limiting, logging, or alerts? Explore our automation & AI guides next!
0 Comments