Course contentsShow
← Node.js
Lesson 1348 of 1,98012. Caching Strategies and Application SpeedFree lesson

Rate Limiting via Cache Counters

You will use fast cache access to enforce API usage limits per user.

In this lesson, you will learn how to leverage high-speed cache counters to effectively throttle incoming API requests and protect your backend services.

Why Cache for Rate Limiting?

When managing API traffic, you need a mechanism that is both lightning-fast and globally accessible. Checking your primary database on every single request to see if a user has exceeded their quota is too slow and puts unnecessary strain on your persistence layer. By using a fast, memory-based cache like Redis, you can increment counters in constant time (O(1)O(1)) and decide whether to block or allow a request in microseconds.

The Counter Pattern

The logic is straightforward: assign each user a unique key based on their ID or IP address (e.g., rate_limit:user:123). When a request arrives, you increment this key in the cache and set an expiration time (TTL) if it’s the first request of a new window.

Here is how you might implement a basic fixed-window limiter:

async function isAllowed(userId) {
  const key = `rate_limit:${userId}`;
  
  // Increment the counter
  const currentCount = await redis.incr(key);

  // Set TTL only on the first request of the window (1 minute)
  if (currentCount === 1) {
    await redis.expire(key, 60);
  }

  // If count exceeds threshold, reject the request
  return currentCount <= 100;
}

Atomic Operations

The beauty of this approach lies in the atomicity of the INCR command. In a distributed Node.js environment, multiple instances of your application might receive requests from the same user simultaneously. Because INCR is an atomic operation within the cache, you don't have to worry about race conditions where two processes read the same value, increment it, and write it back incorrectly. The cache acts as the "source of truth" for the user's current request tally.

When the TTL expires, the key is automatically deleted, effectively resetting the counter for the next window. This pattern is exceptionally efficient because it keeps your application code clean and offloads the heavy lifting of state management to the cache layer.

Key Takeaway: Use atomic cache increments combined with TTL expiration to enforce scalable, high-performance rate limits without putting stress on your primary database.