Security News
pnpm 10.0.0 Blocks Lifecycle Scripts by Default
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.
express-throttle
Advanced tools
Request throttling middleware for Express framework
$ npm install express-throttle
The throttling is done using the canonical token bucket algorithm, where tokens are "refilled" in a sliding window manner by default (can be configured to fixed time windows). This means that if we a set maximum rate of 10 requests / minute, a client will not be able to send 10 requests 0:59, and 10 more 1:01. However, if the client sends 10 requests at 0:30, he will be able to send a new request at 0:36 (since tokens are refilled continuously 1 every 6 seconds).
By default, throttling data is stored in memory and is thus not shared between multiple processes. If your application is behind a load balancer which distributes traffic among several node processes, then throttling will be applied per process, which is generally not what you want (unless you can ensure that a client always hits the same process). It is possible to customize the storage so that the throttling data gets saved to a shared backend (e.g Redis). However, the current implementation contains a race-condition and will likely fail (erroneously allow/block certain requests) under high load. My plan is to address this shortcoming in future versions.
TL;DR - Use this package in production at your own risk, beware of the limitations.
var express = require("express");
var throttle = require("express-throttle");
var app = express();
// Throttle to 5 reqs/s
app.post("/search", throttle("5/s"), function(req, res, next) {
// ...
});
// ...using fixed time windows instead
app.post("/search", throttle("5/s:fixed"), function(req, res, next) {
// ...
})
Combine it with a burst capacity of 10, meaning that the client can make 10 requests at any rate. The capacity is "refilled" with the specified rate (in this case 5/s).
app.post("/search", throttle({ "rate": "5/s", "burst": 10 }), function(req, res, next) {
// ...
});
// "Half" requests are supported as well (1 request every other second)
app.post("/search", throttle({ "rate": "1/2s", "burst": 5 }), function(req, res, next) {
// ...
});
By default, throttling is done on a per ip-address basis (respecting the X-Forwarded-For header). This can be configured by providing a custom key-function:
var options = {
"rate": "5/s",
"burst": 10,
"key": function(req) {
return req.session.username;
}
};
app.post("/search", throttle(options), function(req, res, next) {
// ...
});
The "cost" per request can also be customized, making it possible to, for example whitelist certain requests:
var whitelist = ["ip-1", "ip-2", ...];
var options = {
"rate": "5/s",
"burst": 10,
"cost": function(req) {
var ip_address = req.connection.remoteAddress;
if (whitelist.indexOf(ip_address) >= 0) {
return 0;
} else if (req.session.is_privileged_user) {
return 0.5;
} else {
return 1;
}
}
};
app.post("/search", throttle(options), function(req, res, next) {
// ...
});
var options = {
"rate": "1/s",
"burst": 10,
"cost": 2.5 // fixed costs are also supported
};
app.post("/expensive", throttle(options), function(req, res, next) {
// ...
});
Throttled requests will simply be responded with an empty 429 response. This can be overridden:
var options = {
"rate": "5/s",
"on_throttled": function(req, res, next, bucket) {
// Possible course of actions:
// 1) Log request
// 2) Add client ip address to a ban list
// 3) Send back more information
res.set("X-Rate-Limit-Limit", 5);
res.set("X-Rate-Limit-Remaining", 0);
res.status(503).send("System overloaded, try again at a later time.");
}
};
You may also customize the response on requests that are passed through:
var options = {
"rate": "5/s",
"on_allowed": function(req, res, next, bucket) {
res.set("X-Rate-Limit-Limit", 5);
res.set("X-Rate-Limit-Remaining", bucket.tokens);
res.set("X-Rate-Limit-Reset", bucket.rtime);
}
}
Throttling can be applied across multiple processes. This requires an external storage mechanism which can be configured as follows:
function ExternalStorage(connection_settings) {
// ...
}
// These methods must be implemented
ExternalStorage.prototype.get = function(key, callback) {
fetch(key, function(bucket) {
// First argument should be null if no errors occurred
callback(null, bucket);
});
}
ExternalStorage.prototype.set = function(key, bucket, callback) {
save(key, bucket, function(err) {
// err should be null if no errors occurred
callback(err);
});
}
var options = {
"rate": "5/s",
"store": new ExternalStorage()
}
app.post("/search", throttle(options), function(req, res, next) {
// ...
});
rate
: Determines the number of requests allowed within the specified time unit before subsequent requests get throttled. Must be specified according to the following format: X/Yt(:fixed)
where X and Y are integers and t is the time unit which can be any of the following: ms, s, sec, second, m, min, minute, h, hour, d, day
If you prefer tokens to be refilled in fixed intervals, append :fixed
. E.g 5/min:fixed
.
burst
: The number of requests that can be made at any rate. Defaults to X as defined above.
store
: Custom storage class. Must implement a get
and set
method with the following signatures:
// callback in both methods must be called with an error (if any) as first argument
function get(key, callback) {
fetch(key, function(err, bucket) {
if (err) callback(err);
else callback(null, bucket);
});
}
function set(key, bucket, callback) {
// 'bucket' will be an object with the following structure:
/*
{
"size": Number, (max number of tokens this bucket can have)
"period": Number, (time in ms it takes to replenish all tokens)
"tokens": Number, (current number of tokens)
"mtime": Number, (last modification time)
"rtime": Number (time until next reset)
}
*/
save(key, bucket, function(err) {
callback(err);
}
}
Defaults to an LRU cache with a maximum of 10000 entries.
key
: Function used to identify clients. It will be called with an express request object. Defaults to:
function(req) {
return req.headers["x-forwarded-for"] || req.connection.remoteAddress;
}
cost
: Number or function used to calculate the cost for a request with an express request object. Defaults to 1.
on_allowed
: A function called when the request is passed through with an express request object, express response object, next
function and a bucket
object. Defaults to:
function(req, res, next, bucket) {
next();
}
on_throttled
: A function called when the request is throttled with an express request object, express response object, next
function and a bucket
object. Defaults to:
function(req, res, next, bucket) {
res.status(429).end();
};
FAQs
Request throttling middleware for Express
The npm package express-throttle receives a total of 5,399 weekly downloads. As such, express-throttle popularity was classified as popular.
We found that express-throttle demonstrated a not healthy version release cadence and project activity because the last version was released 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
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.
Product
Socket now supports uv.lock files to ensure consistent, secure dependency resolution for Python projects and enhance supply chain security.
Research
Security News
Socket researchers have discovered multiple malicious npm packages targeting Solana private keys, abusing Gmail to exfiltrate the data and drain Solana wallets.