Skip to content

Shr1yaK/RateGuard

Repository files navigation

RateGuard

A token-bucket API rate limiter built as Express middleware in TypeScript.

Each client (identified by API key) gets an independent token bucket. Requests cost one token; tokens refill continuously over time. When a bucket runs dry the request is rejected with 429 Too Many Requests. This lets clients burst up to capacity while capping their sustained rate — the same approach production APIs use.

Features

  • Token bucket algorithm — continuous, lazy refill (no background timers)
  • Middleware architecture — rate limiting is middleware, not endpoint logic
  • API key isolation — one independent bucket per client
  • Configurable limits — capacity and refill rate via environment variables
  • Standard headersX-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
  • Request IDs — every response carries an X-Request-ID (echoed or generated)
  • Swappable strategy — everything sits behind an IRateLimiter interface
  • Tested — unit tests for the bucket, integration tests for the HTTP layer

Architecture

Client
  |
  |  HTTP request
  v
Express
  |
  v
requestId middleware      attach / echo X-Request-ID
  |
  v
auth middleware           x-api-key present?  --no-->  401
  |
  v
rate limiter middleware   bucket.consume()?
  |                            |
  |  yes                       |  no
  v                            v
route handler              429 Too Many Requests
  |
  v
response (+ X-RateLimit-* headers)

The rate limiter is a middleware in the chain, so every protected route is guarded the same way without touching the handler.

Getting started

npm install

# optional: copy the example env (defaults are fine without it)
cp .env.example .env

# run in watch mode
npm run dev

The server logs:

RateGuard listening on http://localhost:3000 (capacity=10, refillRate=2/s)

Usage

# missing key -> 401
curl -i http://localhost:3000/hello

# with a key -> 200 + rate-limit headers
curl -i -H "x-api-key: demo-key" http://localhost:3000/hello

# hammer it: first 10 succeed, then 429
for i in $(seq 1 12); do
  curl -s -o /dev/null -w "%{http_code}\n" -H "x-api-key: demo-key" http://localhost:3000/hello
done

GET /hello

Protected by auth + rate limiting. Returns { "message": "Hello World", ... }.

Success headers

Header Meaning
X-RateLimit-Limit Bucket capacity
X-RateLimit-Remaining Tokens left
X-RateLimit-Reset Unix time when the next token is available
X-Request-ID Request identifier (echoed or generated)

Rate-limited response (429)

{
  "error": "Rate limit exceeded",
  "requestId": "",
  "retryAfter": 2,
  "remainingTokens": 0
}

Configuration

Set via environment variables (see .env.example):

Variable Default Description
PORT 3000 Server port
CAPACITY 10 Max tokens per bucket (burst size)
REFILL_RATE 2 Tokens refilled per second

Testing

npm test

Unit tests cover the token bucket (consume, block when empty, time-based refill via fake timers, capacity cap). Integration tests (Supertest) cover auth, the rate-limit headers, the 10-then-429 boundary, per-key isolation, and request IDs.

Project structure

src/
  app.ts            Express app + middleware wiring
  server.ts         entry point
  config.ts         env-driven configuration
  limiter/
    IRateLimiter.ts strategy interface
    TokenBucket.ts  token bucket implementation
    BucketStore.ts  one bucket per api key
  middleware/
    requestId.ts    attach / echo X-Request-ID
    auth.ts         api key check
    rateLimiter.ts  consume a token or return 429
  routes/
    api.ts          GET /hello
  models/
    RequestMetadata.ts
  utils/
    time.ts
tests/

Tech stack

TypeScript, Node.js, Express, Jest + Supertest, ESLint, Prettier, uuid.

About

High-performance distributed API rate limiter built with TypeScript featuring sliding-window rate limiting, Redis-backed storage, and middleware integration.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors