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 ) => { /* ... */ }
)