Redis Patterns in Node.js

Redis image of article

Rate Limiting

Rate limiting is a common use case for Redis. It allows you to limit the number of requests a user can make in a given time period. This can help prevent abuse of your API and ensure that your servers are not overwhelmed. For example, for authentication requests, you can limit the number of login attempts a user can make in a given time period. If the user exceeds the limit, you can block further login attempts for a certain period of time. There is two type of rate limiting:

  • Fixed Window
  • Sliding Window

Fixed Window

In the fixed window algorithm, you divide the time period into fixed intervals (e.g., 1 minute). For each interval, you keep track of the number of requests made by the user. If the user exceeds the limit in a given interval, you block further requests until the next interval starts. Here is an example of how you can implement fixed window rate limiting with Redis in Node.js:

/**
 * Record a hit against a unique resource that is being
 * rate limited.  Will return -1 when the resource has hit
 * the rate limit.
 * @param ipAddress - the unique name of the resource.
 * @param opts - object containing interval and maxHits details:
 *   {
 *     interval: 1,
 *     maxHits: 5
 *   }
 * @returns Promise that resolves to number of hits remaining,
 *   or 0 if the rate limit has been exceeded..
 */
async function hitFixedWindow(
  client: Redis,
  ipAddress: string,
  opts: { interval: number; maxHits: number },
): Promise<number> {
  const key = `limiter:fixed-window:${ipAddress}`;
  const pipeline = client.pipeline();
  pipeline.incr(key);
  pipeline.expire(key, opts.interval);

  const results = await pipeline.exec();
  const hits = results?.[0]?.[1];

  if (!isTypeOfNumber(hits)) {
    throw new Error('Redis error');
  }

  let hitsRemaining: number;

  if (hits > opts.maxHits) {
    // Too many hits.
    hitsRemaining = -1;
  } else {
    // Return number of hits remaining.
    hitsRemaining = opts.maxHits - hits;
  }

  return hitsRemaining;
}

Sliding Window

In the sliding window algorithm, you keep track of the number of requests made by the user in a rolling window of time. If the user exceeds the limit in the rolling window, you block further requests until the window resets. Here is an example of how you can implement sliding window rate limiting with Redis in Node.js:

const hitSlidingWindow = async (
  client: Redis,
  ipAddress: string,
  opts: {
    interval: number;
    maxHits: number;
  },
) => {
  const key = `limiter:window:${ipAddress}`;
  const now = getCurrentTimestampMs();
  const transaction = client.multi();

  const results = await transaction
    .zremrangebyscore(key, '-inf', now - opts.interval)
    .zadd(key, now, now.toString())
    .zcard(key)
    .exec();

  const rangeByScore = results?.[0]?.[1];
  const added = results?.[1]?.[1];
  const hits = results?.[2]?.[1];

  if (
    !isTypeOfNumber(rangeByScore) ||
    !isTypeOfNumber(added) ||
    !isTypeOfNumber(hits)
  ) {
    throw new AppError('Redis error', 500);
  }

  if (hits > opts.maxHits) {
    return -1;
  }

  return opts.maxHits - hits;
};