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

flex-rate-limit

v2.2.4

Published

Universal rate limiting module for Node.js - supports any framework, multiple storage backends, flexible algorithms

Readme

flex-rate-limit

A framework-agnostic Node.js rate limiting library with multiple algorithms, Memory / Redis / cache-hub backed storage, and Express-style middleware.

npm version License: Apache-2.0 Node.js Version

Documentation · 中文文档 · Examples

Why Use It

  • Framework-agnostic core: call check() from Express, Koa, Egg.js, Hapi, Fastify, workers, queues, or any custom adapter.
  • Express-style middleware: use limiter.middleware() directly in Express-compatible stacks.
  • Four algorithms: sliding window, fixed window, token bucket, and leaky bucket.
  • Multiple stores: in-memory storage, Redis storage, CacheHubStore, or your own store adapter.
  • Distributed-ready options: use Redis or cache-hub Redis atomic backends when counters must be shared across instances.
  • Standard metadata: each check returns allowed, limit, current, remaining, resetTime, and retryAfter.
  • Type definitions included: CommonJS, ESM, and TypeScript consumers are supported.

Requirements

  • Node.js >=18.0.0
  • npm, pnpm, or another package manager compatible with the npm registry
  • Redis is optional and only needed for Redis-backed distributed rate limiting

Installation

npm install flex-rate-limit

Redis-backed usage:

npm install flex-rate-limit ioredis

cache-hub atomic backend usage:

npm install flex-rate-limit cache-hub ioredis

Quick Start

Direct check()

const { RateLimiter } = require('flex-rate-limit');

const limiter = new RateLimiter({
  windowMs: 15 * 60 * 1000,
  max: 100,
});

const result = await limiter.check('user:123');

if (!result.allowed) {
  console.log(`Retry after ${result.retryAfter} ms`);
}

Express Middleware

const express = require('express');
const { RateLimiter } = require('flex-rate-limit');

const app = express();

const globalLimiter = new RateLimiter({
  windowMs: 15 * 60 * 1000,
  max: 100,
});

app.use(globalLimiter.middleware());

const loginLimiter = new RateLimiter({
  windowMs: 15 * 60 * 1000,
  max: 5,
});

app.post('/api/login', loginLimiter.middleware(), (_req, res) => {
  res.json({ message: 'login accepted' });
});

app.listen(3000);

Koa, Egg.js, Hapi, Fastify, and Other Frameworks

Use check() and map the result to the framework's own middleware, hook, or pre-handler shape:

const { RateLimiter } = require('flex-rate-limit');

const limiter = new RateLimiter({
  windowMs: 60 * 1000,
  max: 60,
});

async function guard(ctx, next) {
  const key = `user:${ctx.user?.id || ctx.ip}:${ctx.path}`;
  const result = await limiter.check(key, { route: ctx.path });

  if (!result.allowed) {
    ctx.status = 429;
    ctx.body = { error: 'Too Many Requests', retryAfter: result.retryAfter };
    return;
  }

  await next();
}

See the runnable framework examples on GitHub and the quickstart guide.

Storage Backends

Memory Store

The default store is fast and simple. Use it for single-process services, local development, tests, and cases where counters do not need to be shared across instances.

const limiter = new RateLimiter({
  windowMs: 60 * 1000,
  max: 100,
  store: 'memory',
});

RedisStore

Use RedisStore when multiple application instances must share counters.

const Redis = require('ioredis');
const { RateLimiter, RedisStore } = require('flex-rate-limit');

const redis = new Redis(process.env.REDIS_URL || 'redis://127.0.0.1:6379');

const limiter = new RateLimiter({
  windowMs: 60 * 1000,
  max: 100,
  store: new RedisStore({ client: redis, prefix: 'rl:' }),
});

If you pass store: 'redis://...', RateLimiter creates and owns the Redis client; call await limiter.close() during shutdown. When you pass your own Redis client to RedisStore, that client remains caller-owned unless you set ownsClient: true.

CacheHubStore

CacheHubStore keeps flex-rate-limit as the algorithm and middleware layer while delegating high-concurrency state updates to cache-hub atomic primitives. Pass a Redis client for distributed production usage; omit the client only when an in-memory cache-hub backend is acceptable. The in-memory cache-hub path prunes expired rate-limit state automatically, and await limiter.close() closes store-owned cleanup timers or cache resources.

const Redis = require('ioredis');
const { RateLimiter, CacheHubStore } = require('flex-rate-limit');

const redis = new Redis(process.env.REDIS_URL || 'redis://127.0.0.1:6379');

const limiter = new RateLimiter({
  algorithm: 'sliding-window',
  windowMs: 60 * 1000,
  max: 100,
  store: new CacheHubStore({ client: redis, prefix: 'rl:' }),
});

Read more in the storage guide.

Algorithms

| Algorithm | Best For | Notes | |-----------|----------|-------| | sliding-window | Precise rolling limits | Default algorithm; stores more per-key state | | fixed-window | High-throughput coarse windows | Fast, but requests can cluster around window boundaries | | token-bucket | Controlled bursts | Allows bursts up to capacity and refills over time | | leaky-bucket | Smoothing traffic | Drains at a steady rate |

Choose semantics first, then optimize storage and hot paths. See algorithm comparison and deep analysis.

Common Configuration

const limiter = new RateLimiter({
  windowMs: 60 * 1000,
  max: 100,
  algorithm: 'sliding-window',
  headers: true,
  keyGenerator: (req) => `user:${req.user?.id || req.ip}:${req.path}`,
  skip: (req) => req.path === '/health',
  handler: (req, res) => {
    res.status(429).json({ error: 'Too Many Requests' });
  },
  perRoute: {
    '/api/login': { max: 5, windowMs: 15 * 60 * 1000 },
    '/api/users': { max: 100, windowMs: 60 * 1000 },
  },
});

| Option | Default | Description | |--------|---------|-------------| | windowMs | 60000 | Time window in milliseconds | | max | 100 | Max requests per window; may be a function | | algorithm | sliding-window | One of the four supported algorithms | | store | memory | Store instance or 'memory' | | keyGenerator | IP-based | Builds the rate-limit key from request context | | skip | () => false | Return true to bypass rate limiting | | handler | built-in 429 response | Custom over-limit handler | | headers | true | Write X-RateLimit-* and Retry-After headers | | perRoute | null | Route-specific overrides | | skipSuccessfulRequests | false | Roll back successful responses | | skipFailedRequests | false | Roll back failed responses |

Full details are in the configuration guide and API reference.

Result Shape

{
  allowed: true,
  limit: 100,
  current: 1,
  remaining: 99,
  resetTime: 1767225600000,
  retryAfter: 0
}

Benchmarks

The repository includes reproducible local benchmark scripts for Memory direct checks, Redis direct checks, and HTTP middleware scenarios.

Run these from a cloned repository after installing development dependencies:

npm install
npm run benchmark:memory
npm run benchmark:redis
npm run benchmark:http

The npm runtime package does not require benchmark dependencies. Redis and HTTP benchmark output records the Node.js version, Redis URL, key distribution, concurrency, and other parameters. Use BENCH_JSON=1 when you need machine-readable output.

See Benchmark and Performance for commands, environment variables, and interpretation notes.

Documentation

| Entry | Description | |-------|-------------| | Documentation index | English documentation navigation | | Chinese documentation | Simplified Chinese documentation navigation | | Quickstart | First integration path and framework examples | | Configuration | Complete option reference and practical presets | | Storage | Memory, Redis, and CacheHubStore selection | | Business lock guide | User + route scoped rate limiting | | Algorithm comparison | Choosing the right algorithm | | API reference | Classes, options, stores, and exports | | Benchmark guide | Local benchmark commands and caveats |

The website is built with Rspress and reuses the docs/ directory:

npm run docs:dev
npm run docs:validate
npm run docs:build

Examples

Runnable examples are available on GitHub:

| Category | Files | |----------|-------| | Quickstart | quickstart-express.js, quickstart-koa.js, quickstart-egg.js, quickstart-hapi.js, quickstart-fastify.js | | Router examples | express-router-example.js, koa-router-example.js, egg-router-example.js, fastify-router-example.js | | IP whitelist examples | express-ip-whitelist-independent.js, koa-ip-whitelist-independent.js, ip-whitelist-example.js | | Standalone usage | standalone-example.js | | Business lock | egg-business-lock-example.js |

Development

npm test
npm run test:unit
npm run test:integration
npm run typecheck
npm run lint
npm run coverage

Troubleshooting

  • Counters are not shared across instances: use RedisStore or CacheHubStore with a Redis client instead of the default Memory store.
  • Redis benchmarks are skipped: start Redis locally or set REDIS_URL / BENCH_REDIS_URL.
  • Package installs but Redis code fails at runtime: install and configure a Redis client such as ioredis.
  • Short-lived scripts keep running after Redis usage: call await limiter.close() when using store: 'redis://...', or pass ownsClient: true to RedisStore if the store should close your client.
  • Koa/Fastify/Hapi integration feels awkward: call check() directly and wrap the result in the framework's native middleware or hook style.
  • Benchmark numbers differ from the docs or CI: local CPU, Node.js version, Redis latency, key distribution, and HTTP app work all affect throughput.

Related Projects

Support

License

Apache-2.0