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

express-rate-limit-redis-slim

v1.0.1

Published

A lightweight, high-performance Redis store for express-rate-limit with atomic Lua scripting

Readme

express-rate-limit-redis-slim

A lightweight, high-performance Redis store for express-rate-limit with atomic Lua scripting.

npm version TypeScript License: MIT

Why This Package?

🚀 Truly "Slim"

Unlike alternatives, this package has zero bloat:

  • Pure TypeScript/ESM with CJS fallback
  • No legacy CommonJS wrappers
  • Minimal dependencies

⚡ Atomic Operations

Traditional Redis rate limiters use separate INCR and EXPIRE commands, creating race conditions:

Request A: INCR key → 1
Request B: INCR key → 2
Request A: EXPIRE key 60  ← What if this fails?
Request B: EXPIRE key 60

Our approach uses a single Lua script that executes atomically:

local count = redis.call('INCR', key)
if count == 1 then
    redis.call('EXPIRE', key, window)
end
return { count, ttl }

This ensures increment and expiry happen in the exact same transaction, preventing "zombie keys" that never expire.

🔌 Flexible Client Support

Works with any Redis client that implements eval():

  • ioredis (recommended)
  • @upstash/redis (perfect for serverless)
  • ✅ Custom clients via sendCommand

Installation

npm install express-rate-limit-redis-slim express-rate-limit

# With ioredis (recommended)
npm install ioredis

# Or with Upstash for serverless
npm install @upstash/redis

Quick Start

With ioredis

import express from 'express';
import { rateLimit } from 'express-rate-limit';
import { RedisStore } from 'express-rate-limit-redis-slim';
import Redis from 'ioredis';

const app = express();

// Create Redis client
const redisClient = new Redis({
  host: 'localhost',
  port: 6379,
});

// Create rate limiter with Redis store
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  limit: 100, // 100 requests per window
  standardHeaders: 'draft-7',
  legacyHeaders: false,
  store: new RedisStore({
    client: redisClient,
    prefix: 'rl:', // Optional, defaults to 'rl:'
  }),
});

app.use(limiter);

app.get('/', (req, res) => {
  res.send('Hello, world!');
});

app.listen(3000);

With @upstash/redis (Serverless)

import { rateLimit } from 'express-rate-limit';
import { RedisStore } from 'express-rate-limit-redis-slim';
import { Redis } from '@upstash/redis';

const upstashRedis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

const limiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  limit: 10,
  store: new RedisStore({
    sendCommand: async (command, args) => {
      if (command === 'EVAL') {
        const [script, numKeys, ...rest] = args;
        return upstashRedis.eval(script as string, rest.slice(0, numKeys as number), rest.slice(numKeys as number));
      }
      throw new Error(`Unsupported command: ${command}`);
    },
  }),
});

With Custom sendCommand

import { RedisStore } from 'express-rate-limit-redis-slim';

const store = new RedisStore({
  sendCommand: async (command, args) => {
    // Your custom Redis implementation
    return await myRedisClient.execute(command, ...args);
  },
});

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | client | RedisClient | - | A Redis client instance with eval() method | | sendCommand | SendCommandFn | - | Alternative: custom function to send Redis commands | | prefix | string | "rl:" | Prefix for all rate limit keys in Redis | | passOnConnectionError | boolean | false | If true, allow requests when Redis is unavailable |

Note: You must provide either client or sendCommand, but not both.


API Reference

RedisStore

Constructor

const store = new RedisStore(options: RedisStoreOptions);

Methods

| Method | Description | |--------|-------------| | init(options) | Called automatically by express-rate-limit | | increment(key) | Increment hit count for a key. Returns { totalHits, resetTime } | | decrement(key) | Decrement hit count (for when a request shouldn't count) | | resetKey(key) | Clear rate limit for a specific key | | shutdown() | Cleanup (no-op, as client lifecycle is external) |


Error Handling

Graceful Degradation

When Redis is unavailable, you can choose to:

  1. Block requests (default): Throws an error, which express-rate-limit handles
  2. Allow requests: Set passOnConnectionError: true
const store = new RedisStore({
  client: redisClient,
  passOnConnectionError: true, // Allow requests if Redis fails
});

Connection Events

Since you own the Redis client, handle connection events yourself:

const redisClient = new Redis();

redisClient.on('error', (err) => {
  console.error('Redis connection error:', err);
});

redisClient.on('connect', () => {
  console.log('Connected to Redis');
});

Performance

This library is optimized for high throughput:

| Benchmark | Operations/sec | |-----------|----------------| | Single key increment | ~50,000/s | | Multi-key concurrent | ~30,000/s |

Tested on Redis 7.0, Node.js 20, local connection

The atomic Lua script executes in a single round-trip, minimizing latency even with remote Redis instances.


TypeScript Support

Full TypeScript support with exported types:

import {
  RedisStore,
  RedisStoreOptions,
  RedisClient,
  SendCommandFn,
  IncrementResponse,
} from 'express-rate-limit-redis-slim';

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting a PR.

# Clone the repo
git clone https://github.com/yourusername/express-rate-limit-redis-slim.git

# Install dependencies
npm install

# Run tests
npm test

# Build
npm run build

License

MIT © 2025