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

nexus-limiter

v1.0.3

Published

Production-ready rate limiter for Node.js — token bucket, sliding window, fixed window. Express, Fastify, NestJS, Hono support. Redis and in-memory stores.

Readme

nexus-limiter

npm version npm downloads license build TypeScript

Production-ready rate limiting for Node.js

Token bucket · Fixed window · Sliding window · Redis · In-memory · Express · Fastify · NestJS · Hono


Why nexus-limiter?

Most rate limiters give you one algorithm and call it done. nexus-limiter gives you four — and lets you swap between them without changing your application code.

| Feature | nexus-limiter | express-rate-limit | |---|---|---| | Native TypeScript | ✅ | ❌ bolted on | | Token bucket (default) | ✅ | ❌ | | Sliding window (accurate) | ✅ | ❌ | | Sliding window (counter) | ✅ | ❌ | | Fixed window | ✅ | ✅ | | Weight per request | ✅ | ❌ | | Plugin system | ✅ | ❌ | | O(1) LRU memory store | ✅ | ❌ | | Redis atomic Lua scripts | ✅ | via plugin | | Express | ✅ | ✅ | | Fastify | ✅ | ❌ | | NestJS | ✅ | ❌ | | Hono | ✅ | ❌ |


Installation

npm install nexus-limiter

# Only if using Redis store
npm install ioredis

Usage with MemoryStore (no extra dependencies)

import { RateLimiter, MemoryStore } from "nexus-limiter";

Usage with RedisStore

import { RateLimiter } from "nexus-limiter";
import { RedisStore } from "nexus-limiter/redis";

Optional peer dependencies

Install only what you need:

# Redis store
npm install ioredis

# Framework adapters — install your framework
npm install express
npm install fastify
npm install hono

Quick start

import { RateLimiter, MemoryStore } from "nexus-limiter";
import { expressRateLimiter } from "nexus-limiter/middleware/express";
import express from "express";

const app = express();

const limiter = new RateLimiter({
  store:     new MemoryStore(),
  limit:     100,        // 100 requests
  windowMs:  60_000,     // per minute
});

app.use(expressRateLimiter(limiter));

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

Algorithms

nexus-limiter supports four rate limiting algorithms. Choose based on your use case.

Token Bucket (default)

Tokens refill continuously at a fixed rate. Allows bursting up to the bucket capacity.

import { RateLimiter, MemoryStore, Algorithm } from "nexus-limiter";

const limiter = new RateLimiter({
  store:     new MemoryStore(),
  limit:     100,       // bucket capacity
  windowMs:  60_000,    // full refill time
  algorithm: Algorithm.TOKEN,
});

Best for: APIs where occasional bursts are acceptable. Smoothest request handling.


Fixed Window

Counts requests in fixed time windows. Resets at window boundaries.

const limiter = new RateLimiter({
  store:     new MemoryStore(),
  limit:     100,
  windowMs:  60_000,
  algorithm: Algorithm.FIXED,
});

Best for: Simple use cases. Lowest memory usage.
Note: Allows up to 2× limit at window boundaries (boundary burst).


Sliding Window Log

Tracks individual request timestamps. Accurate to the millisecond.

const limiter = new RateLimiter({
  store:     new MemoryStore(),
  limit:     100,
  windowMs:  60_000,
  algorithm: Algorithm.SLIDING_LOG,
});

Best for: Strict accuracy requirements. No boundary burst.
Note: Memory usage scales with limit size.


Sliding Window Counter

Approximates sliding window using two fixed-window counters. O(1) memory.

const limiter = new RateLimiter({
  store:     new MemoryStore(),
  limit:     100,
  windowMs:  60_000,
  algorithm: Algorithm.SLIDING_COUNT,
});

Best for: High-scale deployments. ~99% accuracy at O(1) memory.


Stores

Memory Store (default)

In-process store with LRU eviction. No external dependencies.

import { MemoryStore } from "nexus-limiter";

const store = new MemoryStore(
  60_000,   // cleanup interval (ms) — default 60s
  10_000    // max entries before LRU eviction — default 10,000
);

Best for: Single-process applications, development, testing.
Note: Does not share state across multiple processes or servers.


Redis Store

Distributed store with atomic Lua scripts. Prevents race conditions under concurrency.

import { RedisStore } from "nexus-limiter";

// Using URL
const store = new RedisStore({
  url: "redis://:password@localhost:6379",
});

// Using options
const store = new RedisStore({
  host:     "localhost",
  port:     6379,
  password: process.env.REDIS_PASSWORD,
  prefix:   "rl:",  // key prefix — default "rl:"
});

// Inject existing client
import Redis from "ioredis";
const client = new Redis({ host: "localhost" });
const store  = new RedisStore({ client });

Best for: Multi-process, multi-server deployments.
Requires: npm install ioredis


Framework adapters

Express

import { expressRateLimiter } from "nexus-limiter/middleware/express";
// or
import { expressRateLimiter } from "nexus-limiter";

app.use(expressRateLimiter(limiter));

// Route-specific
app.post("/api/login", expressRateLimiter(loginLimiter), handler);

Fastify

import { fastifyRateLimiter } from "nexus-limiter/middleware/fastify";

// Global
app.addHook("onRequest", fastifyRateLimiter(limiter));

// Route-specific
app.get("/api", {
  onRequest: fastifyRateLimiter(limiter)
}, handler);

NestJS

import { RateLimitModule, RateLimitGuard } from "nexus-limiter/middleware/nestjs";
import { MemoryStore } from "nexus-limiter";

// app.module.ts
@Module({
  imports: [
    RateLimitModule.forRoot({
      store:    new MemoryStore(),
      limit:    100,
      windowMs: 60_000,
    })
  ]
})
export class AppModule {}

// Apply to controller
@UseGuards(RateLimitGuard)
@Controller("api")
export class ApiController {}

// Apply to specific route
@UseGuards(RateLimitGuard)
@Get("heavy")
heavyEndpoint() {}

// Apply globally
app.useGlobalGuards(app.get(RateLimitGuard));

Hono

import { honoRateLimiter } from "nexus-limiter/middleware/hono";

app.use("*", honoRateLimiter(limiter));

// Route-specific
app.use("/api/*", honoRateLimiter(apiLimiter));

Configuration

Full configuration reference:

const limiter = new RateLimiter({
  // Required
  store:     new MemoryStore(),  // storage backend
  limit:     100,                // max requests per window
  windowMs:  60_000,             // window duration in milliseconds

  // Algorithm
  algorithm: Algorithm.TOKEN,    // default: TOKEN

  // Request cost
  weight:    1,                  // cost per request — default 1

  // Key generation
  keyGenerator: (req) => req.headers["x-api-key"] ?? req.ip,

  // Skip rate limiting for specific requests
  skip: (req) => req.ip === "127.0.0.1",

  // Headers
  headers: true,                 // send X-RateLimit-* headers — default true

  // Fail behaviour when store is unavailable
  failOpen: true,                // allow requests on store failure — default true

  // Callbacks
  onLimitReached: (req, res, result) => {
    console.log(`Rate limit exceeded for ${result.key}`);
  },

  onError: (error, req, res) => {
    console.error("Rate limiter error:", error);
  },

  // Plugins
  plugins: [LoggerPlugin, MetricsPlugin],
});

Response headers

When headers: true (default), these headers are set on every response:

| Header | Description | |---|---| | X-RateLimit-Limit | Maximum requests allowed | | X-RateLimit-Remaining | Requests remaining in current window | | X-RateLimit-Reset | Unix timestamp when window resets (seconds) | | Retry-After | Seconds until next request allowed (429 only) |


Weight — variable request cost

Charge different costs per endpoint:

const limiter = new RateLimiter({
  store:    new MemoryStore(),
  limit:    100,
  windowMs: 60_000,
  weight:   1,  // default cost
});

// Heavy endpoint costs 10 tokens
app.post("/api/export", expressRateLimiter(
  new RateLimiter({ ...config, weight: 10 })
), handler);

Plugins

Plugins receive lifecycle hooks for every request:

import type { RateLimitPlugin } from "nexus-limiter";

const LoggerPlugin: RateLimitPlugin = {
  name: "logger",

  onRequestStart: (ctx) => {
    console.log(`[${ctx.method}] ${ctx.path} — key: ${ctx.key}`);
  },

  onRequestEnd: (ctx, result) => {
    console.log(`allowed: ${result.allowed}, remaining: ${result.remaining}`);
  },

  onError: (error, ctx) => {
    console.error(`Rate limiter error on ${ctx.path}:`, error);
  },
};

const limiter = new RateLimiter({
  store:   new MemoryStore(),
  limit:   100,
  windowMs: 60_000,
  plugins: [LoggerPlugin],
});

Multiple limiters

Apply different limits to different routes:

const globalLimiter = new RateLimiter({
  store:    new MemoryStore(),
  limit:    1000,
  windowMs: 60_000,
});

const authLimiter = new RateLimiter({
  store:     new RedisStore({ url: REDIS_URL }),
  limit:     5,
  windowMs:  60_000,
  algorithm: Algorithm.SLIDING_LOG,
});

const apiLimiter = new RateLimiter({
  store:     new RedisStore({ url: REDIS_URL }),
  limit:     100,
  windowMs:  60_000,
  algorithm: Algorithm.TOKEN,
  weight:    1,
});

app.use(expressRateLimiter(globalLimiter));
app.post("/auth/login",  expressRateLimiter(authLimiter), loginHandler);
app.use("/api",          expressRateLimiter(apiLimiter));

Key generation

By default, requests are identified by IP address. Customise with keyGenerator:

// By API key
const limiter = new RateLimiter({
  store:    new MemoryStore(),
  limit:    1000,
  windowMs: 60_000,
  keyGenerator: (req) => {
    return req.headers["x-api-key"] as string ?? req.ip;
  },
});

// By user ID (after auth middleware)
const limiter = new RateLimiter({
  store:    new MemoryStore(),
  limit:    100,
  windowMs: 60_000,
  keyGenerator: (req) => req.user?.id ?? req.ip,
});

// By route + IP
const limiter = new RateLimiter({
  store:    new MemoryStore(),
  limit:    50,
  windowMs: 60_000,
  keyGenerator: (req) => `${req.path}:${req.ip}`,
});

Security note: If using x-forwarded-for for IP detection behind a proxy, configure your proxy settings correctly. In Express: app.set("trust proxy", 1). Only trust headers from known proxies.


TypeScript

nexus-limiter is written in TypeScript and ships with full type definitions. No @types package needed.

import type {
  RateLimiterConfig,
  RateLimitResult,
  RateLimitPlugin,
  RateLimitStore,
  AlgorithmConfig,
} from "nexus-limiter";

Error handling

nexus-limiter uses typed errors:

import { RateLimitError } from "nexus-limiter";

const limiter = new RateLimiter({
  store:    new MemoryStore(),
  limit:    100,
  windowMs: 60_000,
  onError:  (error, req, res) => {
    if (error instanceof RateLimitError) {
      console.error(`[${error.code}] ${error.message}`, error.meta);
    }
  }
});

Error codes:

| Code | Description | |---|---| | STORE_ERROR | Storage backend failed | | INVALID_CONFIG | Invalid configuration | | INVALID_STORE | Store doesn't implement required interface | | ALGORITHM_ERROR | Unknown algorithm | | KEY_GENERATION_ERROR | Key generator threw | | INTERNAL_ERROR | Unexpected internal error |


Docker — Redis setup

# docker-compose.yml
services:
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    command: >
      redis-server
      --requirepass ${REDIS_PASSWORD}
      --maxmemory 256mb
      --maxmemory-policy allkeys-lru
docker-compose up -d

Algorithm comparison

| | Token Bucket | Fixed Window | Sliding Log | Sliding Counter | |---|---|---|---|---| | Accuracy | High | Medium | Perfect | ~99% | | Memory | O(1) | O(1) | O(limit) | O(1) | | Boundary burst | No | Yes | No | No | | Best for | General use | Simple limits | Strict APIs | Scale | | Redis atomic | ✅ | ✅ | ✅ | ✅ |


Contributing

Pull requests are welcome. For major changes, open an issue first.

git clone https://github.com/Zayn41/rate-limiter.git
cd rate-limiter
npm install
npm test

License

MIT © Zayn Khan