Neatly Packaged Manuals


    0.5.1 • Public • Published


    A rolling rate limit using Redis. Original idea from Peter Hayes. Uses a lua script for atomic operations and to prevent blocked actions from substracting from the bucket.

    Compatible with ioredis (including in Redis Cluster mode) and node-redis client.


    const RollingLimit = require('redis-token-bucket-ratelimiter');
    const Redis = require('ioredis');
    const redisClient = new Redis({port});
    const myAppVersion = require('./package.json').version;
    const defaultLimiter = new RollingLimit({
      interval: 5000,
      limit: 3,
      redis: redisClient,
      prefix: `${myAppVersion}:`,
      force: false,

    How It Works

    Token Bucket ratelimiters can be described as a bucket within which "tokens" are added at a constant rate. Every time a request is made, a token is removed from the bucket. If the bucket is empty, the request is rejected.

    For instance, one might set a 60/1min request limit by instantiating a limiter like so:

    const requestLimiter = new RollingLimit({
      interval: 60000,
      limit: 60,
      redis: RedisClient

    Then use it as middleware on each request:

    async function rateLimitMiddleware(req, res, next) {
      const id = getUserId(req);
      const limit = await requestLimiter.use(id);
      // Your max tokens
      res.set('X-RateLimit-Limit', String(limit.limit));
      // Remaining tokens; this continually refills
      res.set('X-RateLimit-Remaining', String(limit.remaining));
      // The time at which it's valid to do the same request again; this is almost always now()
      const retrySec = Math.ceil(limit.retryDelta / 1000);
        String(Math.ceil( / 1000) + retrySec)
      if (limit.rejected) {
        res.set('Retry-After', String(retrySec));
          error: {
            message: `Rate limit exceeded, retry in ${retrySec} seconds.`,
            name: 'RateLimitError',

    RollingLimit Types and Methods


    type RollingLimiterOptions = {
      // millisecond duration for the `limit`
      interval: number,
      // maximum of allowed uses in one rolling `interval`
      limit: number,
      // an ioredis or node-redis client instance
      redis: Object,
      // (optional) A string to prepend before `id` for each key
      // Useful for avoiding collisions between applications or versions of an application.
      // A trailing colon is optional and will be added if not present
      prefix?: string,
      // (optional) a boolean to force an accept, but draining the bucket if necessary
      // This allows the limiter to go negative. Use for instances where an action must be allowed,
      // but you still want to deduct from the limit.
      force?: boolean,
    type RollingLimiterResult = {
      limit: number,      // the limit passed into `RollingLimiterOptions` on this invocation
      remaining: number,  // the number of tokens left in the bucket. Can be negative with `force`
      rejected: boolean,  // `true` if the request was rejected, `false` otherwise
      retryDelta: number, // if rejected, milliseconds to wait before making the next request
      forced: boolean,    // if `true`, `force` was on (see `RollingLimiterOptions`)


    limiter = new RollingLimit(options: RollingLimiterOptions)

    Creates a new RollingLimit instance. See types above.

    limiter.use(id: string): Promise<RateLimitResponse>

    limiter.use(id: string, amount?: number): Promise<RateLimitResponse>

    Takes a token from the limit's bucket for id in redis and returns a promise with a RollingLimiterResult object.

    If you want to get the count of tokens left, send in an amount of 0.

    static RateLimiter.stubLimit(max): RateLimitResponse

    Synchronously returns a fake-but-complete response object, with the supplied max for a limit.


    npm i redis-token-bucket-ratelimiter

    DownloadsWeekly Downloads






    Unpacked Size

    18.1 kB

    Total Files


    Last publish


    • strml