-- KEYS[1] = bucket key
-- ARGV: capacity, refillPerSec, now(ms), cost, ttl(sec)
local capacity = tonumber(ARGV[1])
local refill = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local cost = tonumber(ARGV[4])
local ttl = tonumber(ARGV[5])
local state = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local tokens = tonumber(state[1])
local ts = tonumber(state[2])
if tokens == nil then
tokens = capacity
ts = now
end
local elapsed = math.max(0, now - ts) / 1000.0
tokens = math.min(capacity, tokens + (elapsed * refill))
local allowed = 0
if tokens >= cost then
tokens = tokens - cost
allowed = 1
end
redis.call('HMSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('EXPIRE', KEYS[1], ttl)
-- ms until enough tokens accrue for one more request
local deficit = math.max(0, cost - tokens)
local retryMs = 0
if refill > 0 then
retryMs = math.ceil((deficit / refill) * 1000)
end
return { allowed, math.floor(tokens), retryMs }
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const SCRIPT = fs.readFileSync(path.join(__dirname, 'tokenBucket.lua'), 'utf8');
const SCRIPT_SHA = crypto.createHash('sha1').update(SCRIPT).digest('hex');
async function runScript(redis, keys, args) {
try {
return await redis.evalsha(SCRIPT_SHA, keys.length, ...keys, ...args);
} catch (err) {
if (String(err.message).includes('NOSCRIPT')) {
return redis.eval(SCRIPT, keys.length, ...keys, ...args);
}
throw err;
}
}
function rateLimiter(redis, opts) {
const {
name,
capacity,
refillPerSec,
cost = 1,
ttl = Math.ceil((capacity / refillPerSec) * 2),
keyGenerator = (req) => req.ip,
} = opts;
return async function (req, res, next) {
const key = `rl:${name}:${keyGenerator(req)}`;
let allowed, remaining, retryMs;
try {
[allowed, remaining, retryMs] = await runScript(
redis,
[key],
[capacity, refillPerSec, Date.now(), cost, ttl]
);
} catch (err) {
return next(err); // fail open on limiter outage
}
res.set('RateLimit-Limit', String(capacity));
res.set('RateLimit-Remaining', String(remaining));
if (allowed) return next();
const retrySec = Math.ceil(retryMs / 1000);
res.set('Retry-After', String(retrySec));
res.set('RateLimit-Reset', String(retrySec));
return res.status(429).json({
error: 'too_many_requests',
retryAfter: retrySec,
});
};
}
module.exports = { rateLimiter };
const express = require('express');
const Redis = require('ioredis');
const { rateLimiter } = require('./rateLimiter');
const router = express.Router();
const redis = new Redis(process.env.REDIS_URL);
// Tight budget: blunts credential stuffing, keyed by IP.
const loginLimit = rateLimiter(redis, {
name: 'login',
capacity: 5,
refillPerSec: 5 / 60, // ~5 attempts per minute
});
// Generous burst for reads, keyed by API token when present.
const readLimit = rateLimiter(redis, {
name: 'read',
capacity: 100,
refillPerSec: 20,
keyGenerator: (req) => req.get('x-api-key') || req.ip,
});
// Heavier write path charges more tokens per call.
const exportLimit = rateLimiter(redis, {
name: 'export',
capacity: 30,
refillPerSec: 1,
cost: 10,
});
router.post('/login', loginLimit, (req, res) => {
res.json({ ok: true });
});
router.get('/reports', readLimit, (req, res) => {
res.json({ items: [] });
});
router.post('/reports/export', exportLimit, (req, res) => {
res.status(202).json({ queued: true });
});
module.exports = router;
This snippet implements a token-bucket rate limiter as reusable Express middleware backed by Redis, with limits configured per route rather than globally. The token-bucket algorithm models each client's quota as a bucket that refills at a fixed rate up to a maximum capacity; each request removes one token, and requests are rejected when the bucket is empty. Unlike a fixed-window counter, a bucket allows short bursts (up to capacity) while still enforcing a steady long-run rate (refillPerSec), which maps naturally onto how real API traffic behaves.
The core lives in tokenBucket.lua, a Redis script that reads the stored token count and last-refill timestamp, computes how many tokens have accrued since the last call, clamps to capacity, and atomically either debits a token or reports failure. Running the refill-and-debit as a single Lua script is what makes this correct under concurrency: no other client can interleave between the read and the write, so two simultaneous requests can never both consume the last token. The script also sets a TTL so idle buckets expire and Redis memory stays bounded.
In rateLimiter.js, the rateLimiter factory takes per-route options and returns a standard (req, res, next) middleware. It derives a key (by default the client IP scoped to the route), invokes the script via EVALSHA, and on rejection responds with 429 plus the standard RateLimit-* and Retry-After headers so well-behaved clients can back off. The keyGenerator option lets a route key by API token or user id instead of IP, which is important behind proxies where many users share an address. The script is loaded once and cached by SHA to avoid shipping its body on every call.
In routes.js, different endpoints get different budgets: a login route is throttled tightly to blunt credential-stuffing, while a read endpoint gets a generous burst. Because each middleware instance is independent, limits compose cleanly and stay declarative next to the route. A key pitfall to watch is trusting req.ip without configuring trust proxy, and choosing a cost per request when some operations are heavier than others.
Related snips
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
GraphQL API with Spring Boot
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
Share this code
Here's the card — post it anywhere.