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

sliding-window-counter-rate-limiter

v1.0.1

Published

Production-ready Sliding Window Counter rate limiter for Express using Redis and Lua

Readme

Sliding Window Counter Rate Limiter

A production-ready Sliding Window Counter rate limiter for Express applications using Redis and Lua.

It provides atomic rate limiting, configurable client identification, rate-limit headers, Redis failure strategies, and automatic Redis reconnection support.

Features

  • Sliding Window Counter algorithm
  • Redis-backed distributed rate limiting
  • Atomic Redis + Lua execution
  • Express middleware
  • Custom client/key identification
  • Configurable rate limits
  • Configurable Redis key prefix
  • fail-open and fail-closed Redis strategies
  • Rate-limit response headers
  • Retry-After header for blocked requests
  • Input configuration validation
  • Concurrent request safety

Installation

npm install sliding-window-counter-rate-limiter redis

Requirements

  • Node.js 18+
  • Redis 6+
  • Express 4+ or 5+

Basic Usage

const express = require("express");
const { createClient } = require("redis");

const { rateLimiter } = require("sliding-window-counter-rate-limiter");

const app = express();

const redis = createClient({
  url: "redis://localhost:6379",
});

redis.on("error", (error) => {
  console.error("Redis error:", error);
});

redis.connect();

const limiter = rateLimiter({
  redis,
  limit: 100,
  windowMs: 60_000,
});

app.use(limiter);

app.get("/", (req, res) => {
  res.json({
    success: true,
    message: "Request allowed",
  });
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

The above configuration allows:

100 requests
per 60 seconds
per client IP

The default client identifier is:

(req) => req.ip;

Custom Client Identification

You can define how clients should be identified using keyGenerator.

User ID

const limiter = rateLimiter({
  redis,
  limit: 100,
  windowMs: 60_000,

  keyGenerator: (req) => req.user.id,
});

API Key

const limiter = rateLimiter({
  redis,
  limit: 1000,
  windowMs: 60_000,

  keyGenerator: (req) => req.headers["x-api-key"],
});

IP Address

This is the default:

const limiter = rateLimiter({
  redis,
  limit: 100,
  windowMs: 60_000,

  keyGenerator: (req) => req.ip,
});

If your application is behind a reverse proxy, configure Express trust proxy appropriately so req.ip represents the intended client.

Configuration

| Option | Type | Default | Description | | -------------------- | ------------ | --------------- | --------------------------------- | | redis | Redis client | Required | Redis client instance | | limit | number | Required | Maximum requests allowed | | windowMs | number | Required | Rate-limit window in milliseconds | | keyGenerator | function | req => req.ip | Generates the client identifier | | keyPrefix | string | "rate-limit" | Redis key namespace | | redisErrorStrategy | string | "fail-open" | Redis failure behavior |

Example

const limiter = rateLimiter({
  redis,
  limit: 100,
  windowMs: 60_000,
  keyGenerator: (req) => req.user.id,
  keyPrefix: "my-api",
  redisErrorStrategy: "fail-closed",
});

Redis Error Strategies

fail-open

redisErrorStrategy: "fail-open";

If Redis becomes unavailable, the request continues to the application:

Request
   ↓
Rate limiter
   ↓
Redis unavailable
   ↓
next()
   ↓
Application

This prioritizes application availability over strict rate limiting.

fail-closed

redisErrorStrategy: "fail-closed";

If Redis becomes unavailable, the middleware returns:

503 Service Unavailable
{
  "success": false,
  "message": "Rate limiter temporarily unavailable"
}

This prioritizes strict rate-limit enforcement.

Rate-Limit Headers

Successful requests include:

RateLimit-Limit: 100
RateLimit-Remaining: 99
RateLimit-Reset: 42

When the rate limit is exceeded:

HTTP/1.1 429 Too Many Requests
Retry-After: 42

Response:

{
  "success": false,
  "message": "Too many requests"
}

How Sliding Window Counter Works

The algorithm divides time into fixed windows.

For example, with a 60-second window:

Previous Window              Current Window
───────────────────          ───────────────────
       60 seconds                    60 seconds

Instead of completely ignoring the previous window, the algorithm gives it a decreasing weight.

The estimated request count is:

estimatedCount =
    previousCount × (1 - progress)
    + currentCount

Where:

progress =
    elapsedTime / windowSize

Example:

Limit = 10

Previous window = 6 requests
Current window = 2 requests

50% of current window has elapsed

Previous contribution:
6 × (1 - 0.5)
= 3

Estimated count:
3 + 2
= 5

The request is allowed because:

5 < 10

The Redis Lua script performs the calculation and counter update atomically.

Why Redis + Lua?

The rate limiter performs multiple operations:

Read current counter
Read previous counter
Calculate weighted count
Check limit
Increment counter
Set expiration

These operations need to behave atomically when many requests arrive concurrently.

Redis executes the Lua script atomically, preventing race conditions such as multiple concurrent requests all observing the same counter value before incrementing it.

Redis Key Structure

Keys follow this structure:

{keyPrefix}:{clientKey}:{windowStart}

Example:

rate-limit:user-123:1757750400000

Previous and current windows use separate Redis keys.

Testing

Run the test suite with:

npm test

The project includes tests for:

  • Basic rate limiting
  • Window expiration
  • Sliding-window behavior
  • Previous-window weighting
  • Concurrent requests
  • Client isolation
  • Redis failure handling
  • Rate-limit headers
  • Configuration validation
  • Public package API

License

MIT