-
-
Notifications
You must be signed in to change notification settings - Fork 189
Memory
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
});
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.
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.
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.
-
expired —
expiresAtis 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/expiresAthas 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: [] }
// }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'));-
keyPrefix is stripped on dump and re-applied on restore. So a snapshot taken with
keyPrefix: 'a'and restored into a limiter withkeyPrefix: 'b'works correctly. The records end up stored underb:andlimiter2.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'sduration. 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 whoseexpiresAthas already passed by the timerestore()runs are dropped (counted asexpired). -
Configuration changes are not reconciled. If you dump from a limiter with
points: 10, restore into one withpoints: 5, and a key already hadvalue: 7consumed, the nextconsume()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.
Get started
Middlewares and plugins
Migration from other packages
Limiters:
- Cluster
- Drizzle
- DynamoDB
- Etcd
- Memcached
- Memory
- MongoDB (with sharding support)
- MySQL
- PM2 Cluster
- PostgreSQL
- Prisma
- Redis
- SQLite
- Valkey: iovalkey and Valkey Glide
- BurstyRateLimiter
- RateLimiterUnion
- RateLimiterQueue
Wrappers:
- AWS SDK v3 Client Rate Limiter
- RLWrapperBlackAndWhite Black and White lists
- RLWrapperTimeouts Timeouts
Knowledge base:
- Block Strategy in memory
- Insurance Strategy
- Periodic sync to reduce number of requests
- Comparative benchmarks
- Smooth out traffic peaks
-
Usage example
- Minimal protection against password brute-force
- Login endpoint protection
- Websocket connection prevent flooding
- Dynamic block duration
- Different limits for authorized users
- Different limits for different parts of application
- Block Strategy in memory
- Insurance Strategy
- Third-party API, crawler, bot rate limiting