Security News
Combatting Alert Fatigue by Prioritizing Malicious Intent
In 2023, data breaches surged 78% from zero-day and supply chain attacks, but developers are still buried under alerts that are unable to prevent these threats.
rate-limiter-flexible
Advanced tools
Flexible API rate limiter backed by Redis for distributed node.js applications
The rate-limiter-flexible npm package is a powerful and flexible rate limiting library for Node.js. It supports various backends like Redis, MongoDB, and in-memory storage, making it suitable for distributed systems. It helps in controlling the rate of requests to APIs, preventing abuse, and ensuring fair usage.
Basic Rate Limiting
This feature allows you to set up basic rate limiting using in-memory storage. The example limits a user to 5 requests per second.
const { RateLimiterMemory } = require('rate-limiter-flexible');
const rateLimiter = new RateLimiterMemory({
points: 5, // 5 points
duration: 1, // Per second
});
rateLimiter.consume('user-key')
.then(() => {
// Allowed
})
.catch(() => {
// Blocked
});
Rate Limiting with Redis
This feature demonstrates how to use Redis as a backend for rate limiting. The example limits a user to 10 requests per minute.
const { RateLimiterRedis } = require('rate-limiter-flexible');
const Redis = require('ioredis');
const redisClient = new Redis();
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
points: 10, // 10 points
duration: 60, // Per minute
});
rateLimiter.consume('user-key')
.then(() => {
// Allowed
})
.catch(() => {
// Blocked
});
Rate Limiting with MongoDB
This feature shows how to use MongoDB as a backend for rate limiting. The example limits a user to 5 requests per minute.
const { RateLimiterMongo } = require('rate-limiter-flexible');
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/rate-limiter', { useNewUrlParser: true, useUnifiedTopology: true });
const rateLimiter = new RateLimiterMongo({
storeClient: mongoose.connection,
points: 5, // 5 points
duration: 60, // Per minute
});
rateLimiter.consume('user-key')
.then(() => {
// Allowed
})
.catch(() => {
// Blocked
});
Rate Limiting with Bursts
This feature allows for burst handling by blocking the user for a specified duration if they exceed the rate limit. The example blocks a user for 10 seconds if they exceed 10 requests per second.
const { RateLimiterMemory } = require('rate-limiter-flexible');
const rateLimiter = new RateLimiterMemory({
points: 10, // 10 points
duration: 1, // Per second
blockDuration: 10, // Block for 10 seconds if consumed more than points
});
rateLimiter.consume('user-key')
.then(() => {
// Allowed
})
.catch(() => {
// Blocked
});
express-rate-limit is a basic rate-limiting middleware for Express applications. It is simpler and less flexible compared to rate-limiter-flexible, but it is easier to set up for basic use cases.
rate-limiter is another rate limiting library for Node.js. It is less feature-rich compared to rate-limiter-flexible and does not support as many backends, but it is straightforward to use for simple rate limiting needs.
bottleneck is a powerful rate limiting and job scheduling library for Node.js. It offers more advanced features like priority queues and job scheduling, making it more suitable for complex use cases compared to rate-limiter-flexible.
Flexible rate limiter and anti-DDoS protector works in process Memory, Cluster, MongoDB, MySQL or Redis allows to control requests rate in single process or distributed environment.
It uses fixed window as it is much faster than rolling window. See comparative benchmarks with other libraries here
:star: It is STARving, don't forget to feed the beast! :star:
Advantages:
insuranceLimiter
set upblock
, penalty
and reward
methodsAverage latency during test pure NodeJS endpoint in cluster of 4 workers with everything set up on one server by
1000 concurrent clients with maximum 2000 requests per sec during 30 seconds.
1. Memory 0.34 ms
2. Cluster 0.69 ms
3. Redis 2.45 ms
4. Mongo 4.75 ms
500 concurrent clients with maximum 1000 req per sec during 30 seconds
5. MySQL 11.10 ms
Endpoint is pure NodeJS endpoint launched in node:latest
and redis:alpine
Docker containers by PM2 with 4 workers
By bombardier -c 1000 -l -d 30s -r 2000 -t 5s http://127.0.0.1:8000
Test with 1000 concurrent requests with maximum 2000 requests per sec during 30 seconds
Statistics Avg Stdev Max
Reqs/sec 2015.20 511.21 14570.19
Latency 2.45ms 7.51ms 138.41ms
Latency Distribution
50% 1.95ms
75% 2.16ms
90% 2.43ms
95% 2.77ms
99% 5.73ms
HTTP codes:
1xx - 0, 2xx - 53556, 3xx - 0, 4xx - 6417, 5xx - 0
npm i rate-limiter-flexible
Redis >=2.6.12
It supports both redis
and ioredis
clients.
Redis client must be created with offline queue switched off.
const redis = require('redis');
const redisClient = redis.createClient({ enable_offline_queue: false });
const Redis = require('ioredis');
const redisClient = new Redis({
options: {
enableOfflineQueue: false
}
});
const { RateLimiterRedis, RateLimiterMemory } = require('rate-limiter-flexible');
// It is recommended to process Redis errors and setup some reconnection strategy
redisClient.on('error', (err) => {
});
const opts = {
// Basic options
redis: redisClient,
points: 5, // Number of points
duration: 5, // Per second(s)
// Custom
execEvenly: false, // Do not delay actions evenly
blockDuration: 0, // Do not block if consumed more than points
keyPrefix: 'rlflx', // must be unique for limiters with different purpose
// Redis and Mongo specific
inmemoryBlockOnConsumed: 10, // If 10 points consumed in current duration
inmemoryBlockDuration: 30, // block for 30 seconds in current process memory
insuranceLimiter: new RateLimiterMemory(
// It will be used only on Redis, Mongo or MySQL error as insurance
// Can be any implemented limiter like RateLimiterMemory or RateLimiterRedis extended from RateLimiterAbstract
{
points: 1, // 1 is fair if you have 5 workers and 1 cluster
duration: 5,
execEvenly: false,
})
};
const rateLimiterRedis = new RateLimiterRedis(opts);
rateLimiterRedis.consume(remoteAddress)
.then((rateLimiterRes) => {
// ... Some app logic here ...
// Depending on results it allows to fine
rateLimiterRedis.penalty(remoteAddress, 3)
.then((rateLimiterRes) => {});
// or rise number of points for current duration
rateLimiterRedis.reward(remoteAddress, 2)
.then((rateLimiterRes) => {});
})
.catch((rejRes) => {
if (rejRes instanceof Error) {
// Some Redis error
// Never happen if `insuranceLimiter` set up
// Decide what to do with it in other case
} else {
// Can't consume
// If there is no error, rateLimiterRedis promise rejected with number of ms before next request allowed
const secs = Math.round(rejRes.msBeforeNext / 1000) || 1;
res.set('Retry-After', String(secs));
res.status(429).send('Too Many Requests');
}
});
MongoDB >=3.2
It supports mongodb
native and mongoose
packages
See RateLimiterMongo benchmark here
const { RateLimiterMongo } = require('rate-limiter-flexible');
const mongoose = require('mongoose');
const mongoOpts = {
reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
reconnectInterval: 100, // Reconnect every 100ms
};
mongoose.connect('mongodb://127.0.0.1:27017/' + RateLimiterMongo.getDbName())
.catch((err) => {});
const mongoConn = mongoose.connection;
// Or
const mongoConn = mongoose.createConnection('mongodb://127.0.0.1:27017/' + RateLimiterMongo.getDbName(), mongoOpts);
const opts = {
mongo: mongoConn,
points: 10, // Number of points
duration: 1, // Per second(s)
};
const rateLimiterMongo = new RateLimiterMongo(opts);
// Usage is the same as for RateLimiterRedis
/* --- Or with native mongodb package --- */
const { MongoClient } = require('mongodb');
const mongoOpts = {
useNewUrlParser: true,
reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
reconnectInterval: 100, // Reconnect every 100ms
};
const mongoConn = MongoClient.connect(
'mongodb://localhost:27017',
mongoOpts
);
const opts = {
mongo: mongoConn,
points: 10, // Number of points
duration: 1, // Per second(s)
};
const rateLimiterMongo = new RateLimiterMongo(opts);
// Usage is the same as for RateLimiterRedis
Connection to Mongo takes milliseconds, so any method of rate limiter is rejected with Error, until connection established
insuranceLimiter
can be setup to avoid errors, but all changes won't be written from insuranceLimiter
to RateLimiterMongo
when connection established
It supports mysql2
and mysql
node packages.
MySQL connection have to be created with allowed multipleStatementes
.
Limits data, which expired more than an hour ago, are removed every 5 minutes by setTimeout
.
Read more about RateLimiterMySQL here
const mysql = require('mysql2');
const client = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'secret',
multipleStatements: true // it is required by limiter
});
const opts = {
storeClient: client,
dbName: 'mydb',
tableName: 'mytable', // all limiters store data in one table
points: 5, // Number of points
duration: 1, // Per second(s)
};
const rateLimiter = new RateLimiterMySQL(opts);
// Usage is the same as for RateLimiterRedis
Connection to MySQL takes milliseconds, so any method of rate limiter is rejected with Error, until connection is established
Note: it doesn't work with PM2 yet
RateLimiterCluster performs limiting using IPC. Each request is sent to master process, which handles all the limits, then master send results back to worker.
See RateLimiterCluster benchmark and detailed description here
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
const { RateLimiterClusterMaster, RateLimiterCluster } = require('rate-limiter-flexible');
if (cluster.isMaster) {
// Doesn't require any options, it is only storage and messages handler
new RateLimiterClusterMaster();
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
const rateLimiter = new RateLimiterCluster({
keyPrefix: 'myclusterlimiter', // Must be unique for each limiter
points: 100,
duration: 1,
timeoutMs: 3000 // Promise is rejected, if master doesn't answer for 3 secs
});
// Usage is the same as for RateLimiterRedis
}
It manages limits in current process memory, so keep it in mind when use it in cluster
const rateLimiter = new RateLimiterMemory(
{
keyPrefix: 'rlflx',
points: 1, // 1 is fair if you have 5 workers and 1 cluster, all workers will limit it to 5 in sum
duration: 5,
execEvenly: false,
});
// Usage is the same as for RateLimiterRedis
// Except: it never rejects Promise with Error
Combine 2 or more rate limiters to act as single
Any rate limiters from this rate-limiter-flexible
can be united
Useful for authorization, which must be protected from password brute force
For example, not more than once per second and only 5 points per minute
keyPrefix
is necessary as resolved and rejected results depend on it
const limiter1 = new RateLimiterMemory({
keyPrefix: 'limit1',
points: 1,
duration: 1,
});
const limiter2 = new RateLimiterMemory({
keyPrefix: 'limit2',
points: 5,
duration: 60,
});
const rateLimiterUnion = new RateLimiterUnion(limiter1, limiter2);
rateLimiterUnion.consume(remoteAddress)
.then((res) => {
// Returns object with 2 RateLimiterRes objects
res['limit1'].remainingPoints;
res['limit2'].remainingPoints;
})
.catch((rej) => {
/* Returns object with RateLimiterRes objects only for rejected limiters
* For example:
* { limit1: RateLimiterRes { ... } }
*
* It may be Error if you use Redis, Mongo, MySQL or Cluster without insurance
* { limit2: Error }
*/
});
const rateLimiterMiddleware = (req, res, next) => {
rateLimiter.consume(req.connection.remoteAddress)
.then(() => {
next();
})
.catch((rejRes) => {
res.status(429).send('Too Many Requests');
});
};
app.use(async (ctx, next) => {
try {
await rateLimiter.consume(ctx.ip)
next()
} catch (rejRes) {
ctx.status = 429
ctx.body = 'Too Many Requests'
}
})
keyPrefix
Default: 'rlflx'
If you need to create several limiters for different purpose
points
Default: 4
Maximum number of points can be consumed over duration
duration
Default: 1
Number of seconds before consumed points are reset
execEvenly
Default: false
Delay action to be executed evenly over duration
First action in duration is executed without delay.
All next allowed actions in current duration are delayed by formula msBeforeDurationEnd / (remainingPoints + 2)
It allows to cut off load peaks.
Note: it isn't recommended to use it for long duration, as it may delay action for too long
blockDuration
Default: 0
If positive number and consumed more than points in current duration,
block for blockDuration
seconds.
inmemoryBlockOnConsumed
Default: 0
Against DDoS attacks. Blocked key isn't checked by requesting Redis, MySQL or Mongo.
In-memory blocking works in current process memory.
Redis, MySQL and Mongo are quite fast, however, they may be significantly slowed down on dozens of thousands requests.
inmemoryBlockDuration
Default: 0
Block key for inmemoryBlockDuration
seconds,
if inmemoryBlockOnConsumed
or more points are consumed
insuranceLimiter
Default: undefined
Instance of RateLimiterAbstract extended object to store limits,
when Redis, MySQL or Mongo comes up with any error.
All data from insuranceLimiter
is NOT copied to parent limiter, when error gone
Note: insuranceLimiter
automatically setup blockDuration
and execEvenly
to same values as in parent to avoid unexpected behaviour
storeClient
Required
Have to be mysql2
or mysql
connection
dbName
Default: 'rtlmtrflx'
Database where limits are stored. It is created during creating a limiter
tableName
Default: equals to 'keyPrefix' option
By default, limiter creates table for each unique keyPrefix
.
All limits for all limiters are stored in one table if custom name is set.
timeoutMs
Default: 5000
Timeout for communication between worker and master over IPC.
If master doesn't response in time, promise is rejected with ErrorBoth Promise resolve and reject returns object of RateLimiterRes
class if there is no any error.
Object attributes:
RateLimiterRes = {
msBeforeNext: 250, // Number of milliseconds before next action can be done
remainingPoints: 0, // Number of remaining points in current duration
consumedPoints: 5, // Number of consumed points in current duration
isFirstInDuration: false, // action is first in current duration
}
Returns Promise, which:
RateLimiterRes
when point(s) is consumed, so action can be doneinsuranceLimiter
isn't setup: when some error happened, where reject reason rejRes
is Error objectinsuranceLimiter
isn't setup: when timeoutMs
exceeded, where reject reason rejRes
is Error objectrejRes
is RateLimiterRes
objectrejRes
is RateLimiterRes
objectArguments:
key
is usually IP address or some unique client idpoints
number of points consumed. default: 1
Fine key
by points
number of points for one duration.
Note: Depending on time penalty may go to next durations
Returns Promise, which:
RateLimiterRes
insuranceLimiter
isn't setup: when some error happened, where reject reason rejRes
is Error objectinsuranceLimiter
isn't setup: when timeoutMs
exceeded, where reject reason rejRes
is Error objectReward key
by points
number of points for one duration.
Note: Depending on time reward may go to next durations
Returns Promise, which:
RateLimiterRes
insuranceLimiter
isn't setup: when some Redis error happened, where reject reason rejRes
is Error objectinsuranceLimiter
isn't setup: when timeoutMs
exceeded, where reject reason rejRes
is Error objectBlock key
for secDuration
seconds
Returns Promise, which:
RateLimiterRes
insuranceLimiter
isn't setup: when some error happened, where reject reason rejRes
is Error objectinsuranceLimiter
isn't setup: when timeoutMs
exceeded, where reject reason rejRes
is Error objectMake sure you've launched npm run eslint
before creating PR, all errors have to be fixed.
You can try to run npm run eslint-fix
to fix some issues.
Appreciated, feel free!
FAQs
Node.js rate limiter by key and protection from DDoS and Brute-Force attacks in process Memory, Redis, MongoDb, Memcached, MySQL, PostgreSQL, Cluster or PM
We found that rate-limiter-flexible demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
In 2023, data breaches surged 78% from zero-day and supply chain attacks, but developers are still buried under alerts that are unable to prevent these threats.
Security News
Solo open source maintainers face burnout and security challenges, with 60% unpaid and 60% considering quitting.
Security News
License exceptions modify the terms of open source licenses, impacting how software can be used, modified, and distributed. Developers should be aware of the legal implications of these exceptions.