Rate Limiting
boxExpressRateLimit() — fixed-window rate limiting, keyed by req.ip by default.
Usage
Fixed-window rate limiting, mirroring the npm express-rate-limit package's most commonly used options:
bxsapp.use( boxExpressRateLimit() ) // 100 req/min per req.ip
app.use( "/login", boxExpressRateLimit( { windowMs: 15 * 60000, max: 5 } ) ) // 5 req/15min, scoped to one route
Sets the draft-standard RateLimit-Limit/RateLimit-Remaining/RateLimit-Reset headers (disable with { standardHeaders: false }), and responds 429 with Retry-After once a key's count exceeds max within windowMs. Fixed window, not a sliding one or a token bucket — a client can get up to 2x max requests through right at a window boundary, the same trade-off most minimal in-memory rate limiters make in exchange for O(1) bookkeeping per request.
Options
bxsapp.use( boxExpressRateLimit( {
windowMs: 60000, // 1 minute window
max: 20, // 20 requests per window per key
keyGenerator: ( req ) => req.session.userId ?: req.ip, // key on something other than IP
message: { error: true, message: "Slow down." }
} ) )
The default key is req.ip, so the same caveat as req.ip itself applies: behind a reverse proxy without app.set( "trust proxy", 1 ) (or a list of the proxies' addresses), every request shares the proxy's own IP as the key, rate-limiting the whole app together rather than per client. See Configuration.
Sharing the count across instances
By default the store is in-memory on each RateLimit instance (same trade-off as Session's default MemoryStore — fine for a single-process app, not a cluster). To share the count across instances, name a registered BoxLang cache:
bxsapp.use( "/login", boxExpressRateLimit( { max: 5, windowMs: 15 * 60000, cache: "shared" } ) )
cache takes the same fallback/requireDurable options and degrades the same way as durable sessions (see Falling back without durable storage): with no durable cache it counts per process, with a warning, and never fails the request. store: myStore — any object with hit( key, windowMs ) returning { count, resetAt } — plugs in your own.
The read-then-write against the cache isn't atomic, so under heavy concurrency a limit can run a few requests over, never far under. Every request also costs a cache round trip — a database call for a JDBCStore — so use it on routes that matter (login, signup) rather than as a blanket limit.
Independent limits per route
Each call creates its own counters, so different routes can have independent limits:
bxsapp.post(
"/upload",
boxExpressRateLimit( { windowMs: 5 * 60000, max: 10 } ),
boxExpressUpload( { dest: expandPath( "./uploads" ) } ),
( req, res ) => { /* ... */ }
)