npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

redis-distributed-rate-limiter

v1.3.0

Published

High-performance distributed rate-limiting middleware for Express using atomic Redis Lua scripting and non-blocking telemetry hooks.

Readme

redis-distributed-rate-limiter

A high-performance, ultra-low-latency distributed rate-limiting middleware for Express.js applications. Built with atomic Redis Lua scripting to prevent race conditions across horizontally scaled infrastructure, featuring pluggable rate-limiting algorithms and a decoupled asynchronous telemetry hook for real-time traffic observability.

TypeScript Supported

🏗️ Core Architecture

This library isolates the critical path of API request evaluation from the secondary collection of analytical telemetry data. All three supported algorithms execute atomically within a single Redis Lua script per request, guaranteeing thread safety across any number of horizontally scaled app instances sharing the same Redis backend.

  • Distributed Synchronization: State is centralized in Redis, allowing multiple stateless API gateway instances to scale out horizontally while maintaining shared rate-limit quotas.
  • Pluggable Algorithms: Choose the counting strategy that fits your traffic shape — exact logging, constant-memory counting, or burst-friendly token bucketing — via a single config flag.
  • Atomic Execution: Every algorithm is implemented as a single Lua script (EVAL), so the read-check-write cycle is race-condition-free even under concurrent same-millisecond traffic.
  • Non-Blocking Telemetry Pipelines: Exposes a clean event-driven interface that offloads log recording out of the main request-response lifecycle loop using process.nextTick().
  • Resilient Fail-Open Design: Automatically logs internal exceptions and fails open, preventing cache cluster outages from crashing customer-facing API nodes.
  • Spoof-Resistant IP Resolution: Right-to-left X-Forwarded-For parsing against a configurable trusted-proxy allowlist (with CIDR support), so attackers can't bypass limits by forging headers.

⚙️ Installation

Install the package via the npm registry:

npm install redis-distributed-rate-limiter

Peer Dependencies

Ensure you have the official Redis client installed and running in your root application environment:

npm install redis

🚀 Quick Start (TypeScript / JavaScript)

Initialize the middleware by passing your pre-configured Redis client wrapper and setting up your threshold definitions:

import express from 'express';
import { createClient } from 'redis';
import { distributedRateLimiter, TelemetryLog } from 'redis-distributed-rate-limiter';

const app = express();

// 1. Initialize your centralized Redis client connection
const redisClient = createClient({ url: 'redis://localhost:6379' });
redisClient.connect().then(() => console.log('Redis connected successfully'));

// 2. Configure the Distributed Rate Limiter Middleware
const limiter = distributedRateLimiter({
  redisClient: redisClient,
  algorithm: 'SLIDING_LOG', // optional - see "Choosing an Algorithm" below. Defaults to 'SLIDING_LOG'.
  windowInMs: 60000,   // 1 Minute moving sliding window
  maxRequests: 10,     // Allow up to 10 requests per window
  
  // Custom non-blocking callback hook for async logging infrastructure
  onLog: (logData: TelemetryLog) => {
    // Example: Stream log payloads asynchronously to an Apache Kafka Producer,
    // a microservice logger, or push to an analytics queue.
    console.log(`[Telemetry Ingress] IP: ${logData.ip} | Status: ${logData.status} | Hits: ${logData.currentCount}`);
  }
});

// 3. Apply the middleware cluster globally or to explicit routes
app.use(limiter);

app.get('/api/v1/resource', (req, res) => {
  res.json({ success: true, message: "Welcome to the secure gateway." });
});

// Note: If running behind reverse proxies (AWS ALB, Nginx, Cloudflare, Vercel)
app.set('trust proxy', true);

app.listen(3000, () => console.log('API Gateway active on port 3000'));

🧮 Choosing an Algorithm

As of v1.3.0, algorithm is an optional config flag letting you pick the counting strategy per limiter instance. If omitted, it defaults to 'SLIDING_LOG' — existing installs upgrading from earlier versions behave identically with no config changes required.

const limiter = distributedRateLimiter({
  redisClient,
  algorithm: 'SLIDING_COUNTER', // 'SLIDING_LOG' | 'SLIDING_COUNTER' | 'TOKEN_BUCKET'
  windowInMs: 60000,
  maxRequests: 100,
});

windowInMs and maxRequests keep the same meaning across all three algorithms, so you can switch strategies without restructuring your config: for TOKEN_BUCKET, maxRequests becomes the bucket's max capacity and windowInMs is the time to fully refill an empty bucket (i.e. refillRate = maxRequests / windowInMs).

| Algorithm | Memory / Client | Precision | Best For | | --- | --- | --- | --- | | SLIDING_LOG (default) | O(N) — one entry per request in-window | Exact | Low/medium traffic, or when hard precision matters (billing, abuse-critical endpoints) | | SLIDING_COUNTER | O(1) — 3 fixed fields | Approximate (weighted estimate across the window boundary) | High-traffic services where Redis memory footprint matters more than perfect precision | | TOKEN_BUCKET | O(1) — 2 fixed fields | Exact, burst-aware | Public developer APIs where legitimate clients need to burst (e.g. batch operations) without being punished, while still capping sustained average rate |

A note on SLIDING_COUNTER's tradeoff: it estimates traffic across the previous/current window boundary using a linear weighting formula rather than tracking exact timestamps. This is the standard, well-understood tradeoff of this algorithm (the same approach used in Cloudflare's public rate-limiting design) — it keeps memory flat regardless of request volume, but under a sharp traffic spike right at a window boundary it can permit a short burst modestly above maxRequests. If your endpoint needs a hard, unconditional ceiling, use SLIDING_LOG or TOKEN_BUCKET instead.


🎛️ Configuration Reference

The distributedRateLimiter initialization constructor accepts the following structured parameters:

| Property | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | redisClient | any | Yes | — | An open, active v4 instance connection to your Redis deployment server. | | windowInMs | number | Yes | — | Moving time framework window tracked in milliseconds (e.g., 60000 for 1 minute). For TOKEN_BUCKET, this is the full-refill time. | | maxRequests | number | Yes | — | Total allowed operations inside the specific time frame constraint bounds. For TOKEN_BUCKET, this is the bucket capacity. | | algorithm | 'SLIDING_LOG' \| 'SLIDING_COUNTER' \| 'TOKEN_BUCKET' | No | 'SLIDING_LOG' | Selects the counting algorithm. See Choosing an Algorithm. | | trustedProxies | string[] | No | [] | List of trusted proxy IPs or CIDR subnets (e.g., ['127.0.0.1', '10.0.0.0/8']). Essential for blocking IP header spoofing behind Nginx, AWS, or Cloudflare. | | limitIPv6Subnet | boolean | No | false | Toggles whether to mask native IPv6 range streams to prevent subnet address rotation exploits. | | ipv6Subnet | number | No | 56 | Bitmask routing depth constraints used if limitIPv6Subnet is enabled (replicates express-rate-limit defaults). | | keyGenerator | (req) => string | No | IP-based | Overrides default key tracking logic. Allows tracking limits using authorization tokens, API keys, or User IDs instead of plain IP routing. | | onLog | (log: TelemetryLog) => void | No | — | Callback hook firing asynchronously upon execution completion. Passes complete log payload packages. |

🔒 Securing behind Reverse Proxies (Nginx, Cloudflare, AWS ALB)

If your application sits behind a load balancer or proxy layer, provide your proxy IP blocks to the trustedProxies parameter array. This forces the middleware to safely evaluate the network traversal chain from right-to-left, preventing clients from spoofing their location via artificial X-Forwarded-For entries.

import { distributedRateLimiter } from 'redis-distributed-rate-limiter';

const limiter = distributedRateLimiter({
  redisClient: myRedisInstance,
  windowInMs: 60000,
  maxRequests: 100,
  // Define your trusted infrastructure boundaries
  trustedProxies: ['127.0.0.1', '10.0.0.0/8'],
  limitIPv6Subnet: true
});

⚠️ If trustedProxies is left empty while an X-Forwarded-For header is present, the middleware logs a warning and falls back to the raw TCP socket address, ignoring the header entirely. This is intentional — a misconfigured trust boundary is a bigger risk than an ignored header.


📊 Telemetry Log Payload Schema

The onLog event emitter passes an immutable TelemetryLog object containing downstream performance values:

interface TelemetryLog {
  ip: string;           // Remote user origin IP or mapped proxy client
  path: string;         // Visited API node endpoint URL string
  method: string;       // Executed HTTP request method verb (GET, POST, etc.)
  status: 'ALLOWED' | 'BLOCKED'; // Resolution status string output
  timestamp: number;    // POSIX timestamp tracking exact request ingress time
  currentCount: number; // Requests consumed so far in the current window/bucket, across all algorithms
}

🛡️ Response Headers

Successful operations return standard rate limit metadata embedded securely inside response headers, regardless of which algorithm is active:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 9
X-RateLimit-Reset: 2026-07-05T11:04:48.201Z

When thresholds are broken, the middleware automatically rejects traffic with an explicit 429 Too Many Requests status payload:

{
  "status": 429,
  "error": "Too Many Requests",
  "message": "Rate limit exceeded. Please try again in 60 seconds."
}

🧪 Testing

This package ships with 22 Jest/Supertest integration tests covering:

  • Standard threshold enforcement and 429 responses
  • Dual-stack (IPv4-mapped IPv6) normalization
  • IPv6 subnet-rotation abuse prevention
  • Proxy-spoofing rejection and CIDR-based trust validation
  • Multi-hop trusted proxy chains
  • Fail-open resilience on Redis outages
  • Custom keyGenerator overrides
  • All three algorithms' counting correctness, including window-boundary weighting math (SLIDING_COUNTER) and burst/refill math (TOKEN_BUCKET), verified against a live Redis instance

Run them with:

npm run test