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

rate-shield

v1.0.0

Published

A lightweight, pluggable rate limiter for Node.js using Redis Lua scripts or In-Memory storage.

Readme


Overview

Rate Shield answers one question: "Should I allow or block this request?"

It provides four battle-tested rate limiting algorithms — Fixed Window, Sliding Window, Token Bucket, and Leaky Bucket — each solving the flaws of the last. Start simple with in-memory storage, then scale to Redis with a one-line swap.

Why rate-shield?

  • 🧩 Pluggable — swap storage backends without changing algorithm code
  • 🔒 Atomic Redis support — Lua-scripted operations, zero race conditions
  • 🪶 Zero dependencies (in-memory mode) — drop in anywhere
  • 🏗️ Fully typed — written in TypeScript, ships with declarations
  • Express-ready — drop-in middleware included

Installation

npm install rate-shield
# For Redis support
npm install rate-shield ioredis

Quick Start

import { FixedWindow, MemoryStore } from "rate-shield";

const store = new MemoryStore();
const limiter = new FixedWindow(5, 10_000, store); // 5 requests per 10 seconds

const result = limiter.consume("192.168.1.1");

if (result.allowed) {
  console.log(`✅ Allowed — ${result.remaining} requests remaining`);
} else {
  console.log(`❌ Blocked — retry after ${result.retryAfterMs}ms`);
}

Express Middleware

import express from "express";
import { rateLimit, FixedWindow, MemoryStore } from "rate-shield";

const app = express();

const limiter = new FixedWindow(100, 60_000, new MemoryStore()); // 100 req/min

app.use("/api", rateLimit({
  limiter,
  keyGenerator: (req) => req.ip,  // rate limit by IP
  statusCode: 429,
  errorMessage: "Too many requests. Please slow down.",
}));

app.get("/api/data", (req, res) => res.send("Hello!"));

Redis (Production / Multi-Server)

In-memory stores work for a single process. For distributed setups (multiple servers, serverless), use the atomic Redis limiters powered by Lua scripts — no race conditions, no double-counting.

import { Redis } from "ioredis";
import { FixedWindowRedis, rateLimit } from "rate-shield";

const redis = new Redis("redis://localhost:6379");
const limiter = new FixedWindowRedis(redis, 100, 60_000); // 100 req/min

app.use("/api", rateLimit({ limiter }));

Available Redis limiters: FixedWindowRedis, SlidingWindowRedis, TokenBucketRedis, LeakyBucketRedis


High Availability (Circuit Breaker)

Network partitions happen. If your Redis server goes down, rate-shield won't crash your API. Wrap your Redis limiter in a FallbackLimiter to automatically route traffic to local memory until Redis recovers.

It features fail-open defaults, timeout controls, and a state-machine Circuit Breaker to prevent retry-storms.

import { FallbackLimiter, FixedWindow, MemoryStore } from "rate-shield";
import { FixedWindowRedis } from "rate-shield/redis";

const redisLimiter = new FixedWindowRedis(redis, 100, 60_000);
const memoryLimiter = new FixedWindow(100, 60_000, new MemoryStore());

const haLimiter = new FallbackLimiter(redisLimiter, memoryLimiter, {
  timeoutMs: 50,                // Fallback if Redis takes >50ms
  circuitBreakerErrors: 3,      // Trip circuit after 3 fails
  circuitBreakerCooldownMs: 10_000, // Wait 10s before retrying Redis
  onError: (err, isTripped) => console.error(`Redis failed. Tripped: ${isTripped}`)
});

app.use("/api", rateLimit({ limiter: haLimiter }));

Algorithms

Each algorithm solves a flaw in the previous one. Pick based on your use case.

| Algorithm | Memory | Bursts | Best for | |---|---|---|---| | Fixed Window | Very low | ⚠️ Boundary burst possible | Simple APIs, low-traffic routes | | Sliding Window | Medium (1 entry/req) | ✅ No burst | Accuracy-critical endpoints | | Token Bucket | Very low | ✅ Controlled burst | APIs with variable request costs | | Leaky Bucket | Very low | ❌ Strictly smooth | Constant throughput (queues, streams) |

Fixed Window

Divides time into fixed windows. Counter resets at the boundary.

import { FixedWindow, MemoryStore } from "rate-shield";

const limiter = new FixedWindow(
  5,             // limit: max requests per window
  10_000,        // windowMs: window size in milliseconds
  new MemoryStore()
);

const result = limiter.consume("user-123");

⚠️ Note: Clients can make 2× the allowed requests by hitting the boundary of two consecutive windows. Use Sliding Window if this matters.

Sliding Window

Stores individual request timestamps. The window moves with time — no boundary burst.

import { SlidingWindow, SlidingWindowMemoryStore } from "rate-shield";

const limiter = new SlidingWindow(
  5,             // limit
  10_000,        // windowMs
  new SlidingWindowMemoryStore()
);

const result = limiter.consume("user-123");

Token Bucket

A bucket fills with tokens at a constant rate. Each request consumes one. Allows controlled bursts up to the bucket capacity. Tokens are refilled lazily — no background timers.

import { TokenBucket, TokenBucketMemoryStore } from "rate-shield";

const limiter = new TokenBucket(
  5,             // capacity: max tokens (burst size)
  2,             // refillRate: tokens added per second
  5,             // maxCapacity
  new TokenBucketMemoryStore()
);

const result = limiter.consume("user-123");

Leaky Bucket

Requests fill a bucket that leaks at a constant rate. Enforces perfectly smooth output — no bursts allowed.

import { LeakyBucket, LeakyBucketMemoryStore } from "rate-shield";

const limiter = new LeakyBucket(
  5,             // capacity: max queue depth
  2,             // leakRate: requests drained per second
  new LeakyBucketMemoryStore()
);

const result = limiter.consume("user-123");

API Reference

RateLimitResult

All .consume() calls return:

interface RateLimitResult {
  allowed: boolean;       // true = allow, false = block
  remaining: number;      // requests remaining in this window
  retryAfterMs: number;   // milliseconds to wait before retrying (0 if allowed)
  limit: number;          // the configured max
}

rateLimit(options) — Express Middleware

| Option | Type | Default | Description | |---|---|---|---| | limiter | Limiter | required | Any rate limiter instance | | keyGenerator | (req) => string | req.ip | Identifies the client | | statusCode | number | 429 | HTTP status when blocked | | errorMessage | string | "Too Many Requests" | Response body when blocked |

Custom Storage

Implement the Storage interface to plug in any backend (Redis, Postgres, etc.):

interface Storage {
  get(key: string): FixedWindowState | null;
  set(key: string, value: FixedWindowState): void;
  delete(key: string): void;
}

Architecture

src/
├── types.ts              ← Interfaces & shared contracts
├── storage/
│   └── memoryStore.ts    ← In-memory Map-based stores
├── core/
│   ├── fixedWindow.ts    ← Fixed Window algorithm
│   ├── slidingWindow.ts  ← Sliding Window algorithm
│   ├── tokenBucket.ts    ← Token Bucket algorithm
│   ├── leakyBucket.ts    ← Leaky Bucket algorithm
│   └── fallbackLimiter.ts← Circuit breaker / Failover wrapper
├── redis/
│   ├── index.ts          ← Subpath exporter ('rate-shield/redis')
│   └── atomicLimiter.ts  ← Lua scripts for Redis atomic ops
└── index.ts              ← Barrel exports

| Layer | Responsibility | |---|---| | Types | Contracts — what data looks like | | Storage | Persistence — where state lives (Map or Redis) | | Algorithm | Logic — the allow/block decision | | Resilience| FallbackLimiter keeps APIs online during network crashes |

Algorithms are fully decoupled from storage. Swap MemoryStore for an atomic Redis store without touching core algorithm code.


Development

# Install dependencies
npm install

# Run in development mode
npm run dev

# Build for production
npm run build

# Run tests
npm test

License

MIT © Kru5hna