Back to Blog
Lesson 20 of the Redis: Redis Essentials & Data Types course
DatabasesAugust 7, 20263 min read

Implementing a Basic Rate Limiter with Redis

Learn how to build a production-ready rate limiter using Redis. Master request counting, threshold enforcement, and traffic blocking to secure your API.

RedisNode.jsMiddlewareAPISecurity
Wooden letter tiles forming the word 'LIMITS' on a wooden table.

Previously in this course, we explored the fixed window rate limiting pattern to understand the conceptual approach to traffic control. In this lesson, we are moving from theory to implementation by building a functional, code-driven rate limiter as middleware for your Node.js API.

Understanding the Rate Limiting Workflow

When we talk about rate limiting in an API context, we are essentially implementing a gatekeeper. For every incoming request, our application must perform three distinct steps before deciding whether to process the payload:

  1. Identify the client: Usually via IP address or an API key.
  2. Increment the count: Track the number of requests made within the current time window.
  3. Evaluate the threshold: Compare the current count against a defined limit.

Because Redis is atomic—as discussed in our introduction to atomic operations—it is the perfect tool for this. We avoid "race conditions" where two concurrent requests might read the same count simultaneously and fail to increment correctly.

Implementing the Middleware

We will build a simple Express middleware that intercepts requests. We’ll assume you have already followed the setup in our backend project baseline.

Here is the implementation of a basic rate limiter using the INCR command:

JAVASCRIPT
const redis = require(CE9178">'redis');
const client = redis.createClient();

async function rateLimiter(req, res, next) {
  const ip = req.ip;
  const key = CE9178">`rate_limit:${ip}`;

  // 1. Increment the count for this IP
  const currentRequestCount = await client.incr(key);

  // 2. If it's the first request, set an expiration(TTL)
  if (currentRequestCount === 1) {
    await client.expire(key, 60); // Window: 60 seconds
  }

  // 3. Block if threshold is exceeded(e.g., 10 requests)
  if (currentRequestCount > 10) {
    return res.status(429).send(CE9178">'Too many requests. Please try again later.');
  }

  next();
}

How the Logic Works

  • client.incr(key): This is the atomic engine. It increments the value of the key or creates it with a value of 1 if it doesn't exist.
  • The TTL Strategy: We only call client.expire if the count is 1. This ensures that we don't reset the timer on every single request, which would effectively create a "sliding" window that never actually expires.
  • HTTP 429: Always use the 429 "Too Many Requests" status code. It is the industry standard for informing clients that they are being throttled.

Hands-on Exercise

Modify your current backend project to apply the rateLimiter middleware to a specific "protected" route.

  1. Create a file named middleware/rateLimiter.js and add the code above.
  2. Import this middleware into your main app.js or server.js file.
  3. Apply it to a single route: app.get('/api/data', rateLimiter, (req, res) => { ... }).
  4. Use curl or Postman to spam the endpoint 11 times. Verify that the 11th request receives the 429 error.

Common Pitfalls to Avoid

  • Missing the TTL: If you forget to set an expiration, your Redis keys will grow indefinitely, eventually consuming all your RAM. Always set a TTL on counter keys.
  • Ignoring Distributed Environments: If your application runs on multiple servers, using req.ip is standard, but be aware that load balancers (like Nginx) might report the load balancer's IP instead of the client's. Ensure you trust your proxy headers.
  • Over-limiting: Setting the threshold too low during development can frustrate your testing process. Start with a generous limit (e.g., 100 requests per minute) and tighten it as you go.

FAQ

Does this approach work for all users? It works per IP address. If you need to limit by user account regardless of IP, change the key generation logic to use the user_id instead of req.ip.

Is this safe against brute-force attacks? This is a basic form of protection. For high-security endpoints, consider more robust measures like those outlined in our guide on OWASP security and rate limiting.

Recap

We have successfully implemented a basic rate limiter that uses atomic increments and TTLs to protect our API. By managing traffic at the middleware layer, we ensure that our downstream services remain performant and available for legitimate users.

Up next: We will look at refining this logic to support different thresholds for different API routes, allowing for more granular traffic control.

Similar Posts