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

@srinidhi-kulkarni/rate-limiter

v1.0.3

Published

Distributed Redis-backed Token Bucket Rate Limiter for Node.js and Express

Downloads

47

Readme

@srinidhi/rate-limiter

A high-performance distributed Token Bucket Rate Limiter for Node.js and Express powered by Redis and Lua scripting.

Designed for applications that require atomic, distributed, and race-condition-free rate limiting across multiple server instances.


Why this library?

Traditional in-memory rate limiters work well for a single application instance but become inconsistent when applications are deployed across multiple servers.

This library stores rate limiting state in Redis and executes the complete Token Bucket algorithm using an atomic Redis Lua script, ensuring every application instance shares the same bucket state without race conditions.


Features

  • 🚀 Distributed rate limiting using Redis
  • ⚡ Atomic Token Bucket implementation using Lua
  • 🔒 Race-condition free
  • 🌐 Works across multiple application instances
  • 🧩 Express middleware support
  • 🎯 Per-client configurable limits
  • 📦 Written in TypeScript
  • 🔥 Supports burst traffic
  • ♻️ Automatic token refill
  • ✅ Production-ready architecture

Installation

npm install @srinidhi/rate-limiter

Prerequisites

A running Redis server.

Example:

redis://localhost:6379

Quick Start

import express from "express";
import { RateLimiter } from "@srinidhi/rate-limiter";

const app = express();

const limiter = new RateLimiter({
    redisUrl: "redis://localhost:6379",
});

await limiter.connect();

await limiter.registerClient({
    clientId: "demo-client",
    capacity: 10,
    refillRate: 5,
});

app.use(
    limiter.middleware({
        clientId: (req) =>
            req.headers["x-api-key"] as string,
    })
);

app.get("/", (_, res) => {
    res.json({
        message: "Hello World",
    });
});

app.listen(3000);

How It Works

The library implements the Token Bucket algorithm.

Each client is configured with:

  • Capacity – Maximum number of tokens that can be stored.
  • Refill Rate – Number of tokens regenerated per second.

For every incoming request, the library:

  1. Loads the client's bucket from Redis.
  2. Executes the Token Bucket algorithm atomically using Lua.
  3. Refills tokens based on elapsed time.
  4. Consumes a token if available.
  5. Returns whether the request is allowed.
  6. Stores the updated bucket back in Redis.

Since the entire operation is executed as a single Redis Lua script, concurrent requests cannot corrupt bucket state.


Registering Clients

Each client can have its own rate limiting configuration.

await limiter.registerClient({
    clientId: "free-user",
    capacity: 10,
    refillRate: 5,
});

Example for another plan:

await limiter.registerClient({
    clientId: "pro-user",
    capacity: 100,
    refillRate: 50,
});

Express Middleware

The middleware accepts a callback that extracts a unique identifier for each request.

This identifier can be:

  • API Key
  • User ID
  • JWT Subject
  • IP Address
  • Tenant ID
  • Any unique client identifier

API Key Example

app.use(
    limiter.middleware({
        clientId: (req) =>
            req.headers["x-api-key"] as string,
    })
);

JWT Example

app.use(
    limiter.middleware({
        clientId: (req) => req.user.id,
    })
);

IP Address Example

app.use(
    limiter.middleware({
        clientId: (req) => req.ip,
    })
);

API

Create Rate Limiter

const limiter = new RateLimiter({
    redisUrl: "redis://localhost:6379",
});

connect()

Initializes the Redis connection and loads the Lua script.

await limiter.connect();

registerClient()

Registers a new client.

await limiter.registerClient({
    clientId: "client-1",
    capacity: 20,
    refillRate: 10,
});

updateClient()

Updates an existing client.

await limiter.updateClient({
    clientId: "client-1",
    capacity: 50,
    refillRate: 20,
});

deleteClient()

Deletes the client configuration and bucket.

await limiter.deleteClient("client-1");

check()

Checks whether a request is allowed.

const result = await limiter.check("client-1");

Returns:

{
    allowed: true,
    remainingTokens: 8,
    resetTime: 1740000000000
}

middleware()

Creates Express middleware.

app.use(
    limiter.middleware({
        clientId: (req) =>
            req.headers["x-api-key"] as string,
    })
);

HTTP Response Headers

The middleware automatically adds the following headers.

| Header | Description | |---------|-------------| | X-RateLimit-Limit | Maximum bucket capacity | | X-RateLimit-Remaining | Remaining tokens | | X-RateLimit-Reset | Timestamp when another token becomes available |


Example

Configuration

Capacity = 10
Refill Rate = 5 tokens/sec

Burst of 15 requests

Requests 1–10

✅ Allowed
Requests 11–15

❌ HTTP 429 Too Many Requests

After waiting 2 seconds:

5 tokens/sec

↓

10 tokens regenerated

↓

Requests are allowed again

Error Responses

| Status | Description | |---------|-------------| | 400 | Missing client identifier | | 404 | Client not registered | | 429 | Rate limit exceeded |


Performance

Stress tested using Autocannon.

Example results:

  • ~3,000 Requests/sec
  • Correct burst handling
  • Correct refill behaviour
  • Atomic under concurrency
  • Redis-backed distributed state
  • No race conditions

Why Redis + Lua?

A traditional Redis implementation typically performs multiple operations:

GET

↓

Modify

↓

SET

Under concurrent requests, multiple clients can read the same bucket before it is updated, leading to race conditions.

This library executes the complete Token Bucket algorithm inside a single Redis Lua script.

Benefits:

  • Atomic execution
  • No race conditions
  • No distributed locks
  • High throughput
  • Consistent behaviour across multiple application instances

Requirements

  • Node.js 18+
  • Redis 6+
  • TypeScript 5+

License

MIT


Author

Srinidhi Kulkarni

GitHub: https://github.com/Srinidhi444