Skip to content
Roman edited this page May 6, 2026 · 15 revisions

RateLimiterMemory

It manages limits in current process memory. It expires limited keys by setTimeout. Works in browser as well.

Note: the maximum duration, blockDuration and rateLimiter.block(key, secDuration) for memory limiter is 2147483 seconds or about 24 days because of setTimeout limitation.

const { RateLimiterMemory } = require('rate-limiter-flexible');
const rateLimiter = new RateLimiterMemory({
  points: 6,
  duration: 1,
});
    
rateLimiter.consume(key, 2) // Consume 2 points
  .then((rateLimiterRes) => {
    // Allowed
  })
  .catch((rej) => {
    // Blocked
  });   
    

See all options here

Dump and restore

RateLimiterMemory keeps state in the current process, so by default it is lost on restart. Two methods, dump() and restore(), let you snapshot the state and load it back into a fresh instance. That is useful for graceful restarts, blue/green deploys, or saving a snapshot to disk during shutdown.

Use this when losing up to 1% of request counts won’t affect security or finances, such as in overload or DoS protection.

This is a best-effort persistence mechanism, not a replacement for a shared store. If you need state shared across multiple processes in real time, use Drizzle, Valkey, or another distributed limiter.

dump()

Returns a plain JavaScript object describing every key currently held in memory:

const snapshot = rateLimiter.dump();
// {
//   version: 1,
//   dumpedAt: 1746360000000,
//   storage: [
//     { key: 'user-1', value: 3, expiresAt: 1746360005000 },
//     { key: 'user-2', value: 1, expiresAt: 1746360004500 },
//     ...
//   ]
// }

Each entry stores the consumed points (value) and an absolute expiry timestamp in ms (expiresAt). The snapshot is JSON-safe — JSON.stringify(snapshot) is enough to write it to a file or send it over the wire.

restore(data, detailResponse = false)

Loads a previously dumped snapshot into the limiter. Returns a summary of what happened:

const result = rateLimiter.restore(snapshot);
// { invalid: 0, expired: 2, restored: 14 }

Each entry from the dump falls into exactly one bucket:

  • restored — valid record, not yet expired, loaded into storage.
  • expiredexpiresAt is in the past. The record is dropped silently. This is the normal outcome for keys whose TTL ran out while the process was down.
  • invalid — the entry is not an object, or key/value/expiresAt has the wrong type. Skipped.

If the snapshot itself is missing or has an unsupported version, restore() returns undefined and does not modify state. Corrupt input (a string, null, a non-array storage, etc.) does not throw. It is treated as an empty or invalid snapshot. This makes restore() safe to call on whatever you read back from disk without wrapping it in try/catch for schema errors.

Pass detailResponse = true when you need the actual keys for logging or debugging:

const result = rateLimiter.restore(snapshot, true);
// {
//   restored: { count: 14, keys: ['user-1', 'user-2', ...] },
//   expired:  { count: 2,  keys: ['user-old-1', 'user-old-2'] },
//   invalid:  { count: 0,  keys: [] }
// }

Persisting on shutdown, restoring on startup

The most common pattern: write the snapshot to disk on SIGTERM/SIGINT, read it back when the process boots.

const fs = require('fs');
const { RateLimiterMemory } = require('rate-limiter-flexible');

const SNAPSHOT_PATH = '/var/lib/myapp/rate-limiter.json';

const rateLimiter = new RateLimiterMemory({
  points: 6,
  duration: 1,
});

// Restore on startup
if (fs.existsSync(SNAPSHOT_PATH)) {
  try {
    const snapshot = JSON.parse(fs.readFileSync(SNAPSHOT_PATH, 'utf8'));
    const result = rateLimiter.restore(snapshot);
    if (result) {
      console.log(`Rate limiter restored: ${result.restored} keys, ${result.expired} expired`);
    }
  } catch (err) {
    console.warn('Could not read rate limiter snapshot, starting empty', err);
  }
}

// Dump on shutdown
function persistAndExit(signal) {
  try {
    fs.writeFileSync(SNAPSHOT_PATH, JSON.stringify(rateLimiter.dump()));
  } catch (err) {
    console.error('Failed to write rate limiter snapshot', err);
  }
  process.exit(0);
}

process.on('SIGTERM', () => persistAndExit('SIGTERM'));
process.on('SIGINT',  () => persistAndExit('SIGINT'));

Dump/restore notes and caveats

  • keyPrefix is stripped on dump and re-applied on restore. So a snapshot taken with keyPrefix: 'a' and restored into a limiter with keyPrefix: 'b' works correctly. The records end up stored under b: and limiter2.get('user1') finds them. The dump itself contains bare keys with no prefix information; you cannot use the dump to control which prefix the keys land under, only the receiving limiter's configuration decides that.
  • TTL is recomputed from expiresAt, not from the limiter's duration. If a key was set to expire at T+5s and the process is down for 3s, the restored key expires 2s after restart. Keys whose expiresAt has already passed by the time restore() runs are dropped (counted as expired).
  • Configuration changes are not reconciled. If you dump from a limiter with points: 10, restore into one with points: 5, and a key already had value: 7 consumed, the next consume() will reject. The record is loaded as-is. Account for this when changing limits across deploys.
  • Periodic dumps are not safe under crash. dump() is synchronous and snapshots the current moment. If you want crash resilience rather than graceful-shutdown resilience, snapshot on a timer to disk and accept that you may lose a few seconds of state on a hard kill.
  • Not a substitute for distributed state. Two processes each maintaining their own snapshot will not see each other's counters. Use a shared backend if that matters.

Clone this wiki locally