javascript
56 lines · 7 steps
How token-bucket rate limiting works in Express
A per-user token bucket refills over time and rejects requests once a user runs out of tokens.
Explained by
highlit
1class TokenBucket {
2 constructor({ capacity, refillPerSecond }) {
3 this.capacity = capacity;
4 this.refillPerSecond = refillPerSecond;
5 this.tokens = capacity;
6 this.lastRefill = Date.now();
7 }
8
9 refill() {
10 const now = Date.now();
11 const elapsedSeconds = (now - this.lastRefill) / 1000;
12 if (elapsedSeconds <= 0) return;
13 this.tokens = Math.min(this.capacity, this.tokens + elapsedSeconds * this.refillPerSecond);
14 this.lastRefill = now;
15 }
16
17 tryConsume(cost = 1) {
18 this.refill();
19 if (this.tokens >= cost) {
20 this.tokens -= cost;
21 return true;
22 }
23 return false;
24 }
25}
26
27class RateLimiter {
28 constructor(options) {
29 this.options = options;
30 this.buckets = new Map();
31 }
32
33 bucketFor(userId) {
34 let bucket = this.buckets.get(userId);
35 if (!bucket) {
36 bucket = new TokenBucket(this.options);
37 this.buckets.set(userId, bucket);
38 }
39 return bucket;
40 }
41
42 allow(userId, cost = 1) {
43 return this.bucketFor(userId).tryConsume(cost);
44 }
45}
46
47const limiter = new RateLimiter({ capacity: 10, refillPerSecond: 2 });
48
49export function rateLimit(req, res, next) {
50 const userId = req.user?.id ?? req.ip;
51 if (!limiter.allow(userId)) {
52 res.set('Retry-After', '1');
53 return res.status(429).json({ error: 'Too many requests' });
54 }
55 next();
56}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1The token bucket smooths bursts by allowing up to capacity requests instantly, then throttling to the refill rate.
- 2Computing refill lazily from elapsed time avoids running any background timer.
- 3Keying buckets by user in a Map isolates each caller's quota independently.
Related explainers
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
python
import secrets from django.contrib.auth import authenticate, login from django.core.cache import cache
Two-factor login with OTP in Django
two-factor-auth
one-time-passwords
caching
Intermediate
9 steps
javascript
import { useEffect, useRef, useState } from 'react'; export function useDelayedFlag(active, delay = 300) { const [visible, setVisible] = useState(false);
Delaying a loading spinner with a React hook
custom-hooks
debouncing
cleanup
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-token-bucket-rate-limiting-works-in-express-explained-javascript-0799/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.