Skip to content

RateLimiterQueue

Roman edited this page Jun 8, 2026 · 24 revisions

RateLimiterQueue

RateLimiterQueue limits number of actions during period of time and queues other to execute the next period.

It uses FIFO (first in, first out) queue on single server or distributed environment. It doesn't limit concurrency and it consumes as many tokens as possible, if there are waiting requests.

It strictly respects queue order with Memory and Cluster limiters only. Other limiters respect it too, but do not guarantee: some requests may go out of order because of distributed queue.

Both removeTokens(tokens, key) and getTokensRemaining(key) may be called without key param to use single RateLimiterQueue instance for all users and actions. key can be user ID, IP address or any other string or number.

const http = require('http');
const express = require('express');
const { RateLimiterMemory, RateLimiterQueue } = require('rate-limiter-flexible');

const limiterFlexible = new RateLimiterMemory({
  points: 2,
  duration: 1, // Per one second
});

const limiterQueue = new RateLimiterQueue(limiterFlexible, {
  maxQueueSize: 100, // Default is `4294967295` (2 ^ 32 - 1)
});

const app = express();
app.get('/', async (req, res) => {
  try {
    const remainingTokens = await limiter.removeTokens(1)
    res.end(remainingTokens)
  } catch(err) {
    if (err instanceof Error) {
      res.status(400).end()
    } else {
      res.status(429).send('Too Many Requests');
    }
  }
});

const server = http.createServer(app);
server.listen(3002, () => {
  console.log('RateLimiterQueue service started');
});

In the above example, there is RateLimiterMemory instance created with 2 points available per one second duration. (points and tokens are the same thing in this case).

maxQueueSize option is set to 100. Default value is 4294967295 (2 ^ 32 - 1)

Expiring requests in the queue

const queue = new RateLimiterQueue(limiter)
// reject this request if it hasn't started processing by the given Unix time (seconds)
await queue.removeTokens(1, 'limiter', nowSeconds + 5)

expiresUnixAt sets a deadline as an absolute Unix timestamp in seconds. If the request is still waiting in the queue when that time arrives, it is rejected. If processing starts before the deadline, the request proceeds and may finish either before or after expiresUnixAt.

In the example above, the request waits up to 5 seconds for a token. It's rejected only if it's still queued at that point.

Call removeTokens(tokens, key, expiresUnixAt) or removeTokens(tokens, expiresUnixAt).

When is removeTokens method rejected?

  1. If the queue is full and another request tries to remove token(s), removeTokens immediately rejected with RateLimiterQueueError.
  2. When removeTokens is called with expiresUnixAt more than 0 and item was in a queue by that time.
  3. removeTokens can be also rejected with RateLimiterQueueError, if application tries to remove more tokens than allowed per interval.
  4. If you use one of store limiter like Redis, MongoDB or any other, it may be rejected with error from store.
import { RateLimiterQueueError } from 'rate-limiter-flexible'

getTokensRemaining

const Redis = require('ioredis');
const { RateLimiterRedis, RateLimiterQueue } = require('rate-limiter-flexible');
const redisClient = new Redis({ enableOfflineQueue: false });
const rlRedis = new RateLimiterRedis({
  storeClient: redisClient,
  points: 2, // Number of tokens
  duration: 5, // Per 5 second interval
});
const rlQueue = new RateLimiterQueue(rlRedis);
rlQueue.getTokensRemaining()
  .then((tokensRemaining) => { res.end(tokensRemaining) })
  .catch((errFromStore) => { res.status(500).end() })

Migration from limiter

This RateLimiterQueue provides the same features as rate limiter from limiter package. Advantages in comparison:

  1. Works in multi-server scenario with any store limiter like Redis, MongoDB or any other from rate-limiter-flexible.
  2. Respects queue order with Memory and Cluster limiters.
  3. Works on top of native promises.

Example of migration:

var RateLimiter = require('limiter').RateLimiter;
var limiter = new RateLimiter(150, 'hour');
limiter.removeTokens(1, function(err, remainingRequests) {
  callMyRequestSendingFunction(...);
});

Should be changed to:

const {RateLimiterMemory, RateLimiterQueue} = require('rate-limiter-flexible');
const limiterFlexible = new RateLimiterMemory({
  points: 150,
  duration: 60 * 60, // hour
});
const limiter = new RateLimiterQueue(limiterFlexible);
app.get('/', async (req, res) => {
  const remainingTokens = await limiter.removeTokens(1);
  callMyRequestSendingFunction(...);
})

Scroll top to read more.

Clone this wiki locally