Let me be honest with you.
Every time I start a new Node.js project, I copy-paste this from my last one:
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
});
That 15 * 60 * 1000 has always bothered me. It's not a big deal. But it's also... not great? I have to do math in my head every time I read it. Is that 15 minutes? Let me check. 15 * 60 = 900. 900 * 1000 = 900000. Yes, 15 minutes.
Why am I doing mental arithmetic in 2026?
That small frustration was the start of @chabdulwahab/flowcap.
The actual problem
express-rate-limit is great — 8.6 million weekly downloads great. But it has two issues that quietly annoy me:
1. It's Express-only. If you switch to Fastify or Koa, you need a different package. fastify-rate-limit, koa-ratelimit — each with its own API, its own quirks, its own docs to read.
2. The window is in milliseconds. Every single time. You always end up writing 15 * 60 * 1000 and adding a comment to explain what it means. The comment is the tell — the code isn't readable without it.
rate-limiter-flexible solves the framework problem, but introduces a different one: complexity. It's powerful, but you're reading docs for 20 minutes before you write a single line.
I wanted something in between. Dead simple. Works anywhere. Zero deps. Human-readable.
That's flowcap.
What flowcap does differently
1. Human-readable time windows
// Before — what does this even mean at a glance?
windowMs: 15 * 60 * 1000
// After — obvious
window: '15m'
Supported formats: '500ms', '30s', '15m', '2h', '1d'. Pass a number and it treats it as milliseconds — backwards compatible if you prefer.
2. Works on any framework
The middleware signature is just (req, res, next). That's the universal contract every Node.js framework follows. So flowcap works on all of them:
// Express
app.use(flowcap({ limit: 100, window: '1m' }));
// Fastify
fastify.addHook('onRequest', flowcap({ limit: 100, window: '1m' }));
// Koa
app.use((ctx, next) => flowcap({ limit: 100, window: '1m' })(ctx.req, ctx.res, next));
// Vanilla http
http.createServer((req, res) => {
flowcap({ limit: 100, window: '1m' })(req, res, () => {
// your handler
});
});
3. Built-in presets for common use cases
The most common rate limiting scenarios are always the same: protect your login endpoint, set a standard API limit, lock down sensitive admin routes. So I built presets:
// Brute-force protection on login (5 requests per 15 minutes)
app.post('/login', flowcap.login(), handler);
// Standard API (100 requests per minute)
app.use('/api', flowcap.api());
// Sensitive endpoint (20 per minute)
app.get('/admin', flowcap.strict(), handler);
// High-traffic public route (500 per minute)
app.get('/feed', flowcap.loose(), handler);
Every preset is just a default config — you can override anything:
// Login preset, but stricter
app.post('/login', flowcap.login({ limit: 3 }), handler);
4. IETF standard headers, automatically
Every response gets the RateLimit header from
This is my third indie npm package — the others are @chabdulwahab/env-ok (zero-dep env validator) and @chabdulwahab/api-spy (terminal metrics dashboard). I build small, focused tools that solve one thing well. If that's your kind of software, follow along.
SOCIAL SHARE CARD GENERATOR