Protecting Your API: A Beginner’s Guide to Rate Limiting
Recently, I decided to take some time to learn how rate limiting works. While working on a backend system, I decided to implement rate limiting as a security measure to protect the API from abuse and some bad actors. In this post, I’ll walk through how rate limiting works and how rate limiting works and how I implemented it using express-rate-limit in a Node.js application.
What is Rate Limiting?
When building APIs, you sent to ensure your server can respond to client requests reliably and efficiently. But just like anything in life, moderation is key. You don’t want a single user bombarding your API with hundreds of requests in a short time.
Without guardrails, this could:
- Slow down the experience for other users
- Overload your infrastructure
- Rack up huge costs, especially if you’re using paid services like OpenAI or external APIs
And let’s not forget, some users might intentionally try to exploit your system.
That’s where rate limiting comes in.
Rate limiting is like a bouncer at a club, it controls how many requests each visitor (IP address) can make in a given time.
Real-World Example: The Coffee Shop
Let’s say you’re running a coffee shop:
- Customers can buy up to 5 coffees per hour
- If someone tries to buy a 6th coffee, they’re told to wait
- After an hour, the “coffee count” resets, and they can buy again
Installing the Rate Limiter Package
We’ll use the express-rate-limit middleware for this example
npm install express-rate-limit
What does this package do?
- Tracks request per IP
- Stores request count in memory (or Redis for production)
- Automatically blocks IPs that exceed the limit
Choosing a Rate Limiter Strategy
You should always aim to tailor your limits based on the nature of the request.
| Type of Operation | Strictness | Reason | | --- | --- | --- | | AI API (e.g. GPT-4) | Very strict | Each call costs money | | Login attempts | Strict | Prevent brute-force attacks | | Public endpoints | Moderate | Prevent abuse, maintain performance |
For my project:
- I limit general API calls to 100 requests per 15 minutes.
- I limit AI-related endpoints to just 10 requests per 15 minutes
Example: General API Rate Limiter
import rateLimit from 'express-rate-limit'
// General API Rate Limiter (for all routes)
const generalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes in milliseconds
max: 100, // Maximum 100 requests per IP per 15 minutes
message: {
error: 'Too many requests from this IP, please try again later.',
retryAfter: '15 minutes'
},
standardHeaders: true,
legacyHeaders: false,
// 🧠 What this does:
// - Tracks requests per IP address
// - After 100 requests in 15 minutes, blocks the IP
// - Sends helpful error message with retry time
});
Example: Expensive AI Operation Rate Limiter
// AI Operations Rate Limiter (expensive operations)
const aiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // Only 10 AI requests per IP per 15 minutes
message: {
error: 'Too many AI requests. Please wait before making another request.',
retryAfter: '15 minutes'
},
standardHeaders: true,
legacyHeaders: false,
// �� Why so strict?
// - Each AI request costs money (LLM API)
// - Prevents abuse and cost overruns
// - Protects your business model
});
Applying the Middleware in Express
import express from 'express'
const app = express();
// Apply general rate limiting to ALL routes first
app.use(generalLimiter);
// AI operations (expensive) - 10 requests per 15 minutes
app.use('/api/ai-route-1', aiLimiter);
app.use('/api/ai-route-2', aiLimiter);
What Happens Under the Hood?
- The middleware tracks incoming requests by IP address
- If the count exceeds the limit, the request is blocked with a
429 Too Many Requesterror - The user gets a response explaining when they can try again
Rate Limiting is a simple yet powerful took to secure your API, control costs, and provide a good and fair experience for all users.
Every backend developer should consider implementing this, it’s one of the first defense mechanisms in your API’s security stack.
Refer to this documentation for more insight