process.env.RATE_LIMIT_WINDOW_MS already exists for this. Pull from config so we can tune per-environment without a redeploy.
Same — pull from config. Bonus: per-route overrides.
The get + set below is non-atomic — two parallel requests both read count = 99 and both pass the check. Under load you'll let through 2× the limit on every boundary.
Use redis.incr(key) and redis.expire(key, ttl) in a pipeline (or a single Lua script for atomicity).
Retry-After header.
RFC 6585 expects Retry-After: <seconds> on a 429. Set it to the remaining window so well-behaved clients back off correctly.
If Redis is unavailable, redis.get throws and the request 500s — surprising and bad. Wrap in try/catch and pick a strategy:
• Fail-open (allow on Redis error) — simpler, but lets through unbounded traffic during an outage.
• Fail-closed (deny on Redis error) — safer, but ties uptime of the API to Redis.
Pick one, document it, and emit a metric so we know when it's happening.
Fixed bucket lets through 2× burst at the boundary by design. A sliding window is ~30 lines more code and far smoother. Not blocking, but worth a follow-up ticket.