A self-hosted API rate limiting and quota management service enforcing per-user, per-service request quotas with tier-aware limits, backed by PostgreSQL and Redis.

zAi
A self-hosted API rate limiting and quota management service. zAi enforces per-user, per-service request quotas with tier-aware limits, backed by PostgreSQL for persistence and Redis for atomic counter operations.
About The Project
When a request hits the quota-check endpoint, zAi verifies the caller's JWT, looks up their tier in PostgreSQL, syncs the tier multiplier to Redis, and then runs a single atomic Lua script that increments a per-user, per-service, per-window counter, applies the tier multiplier to the base limit, and returns whether the request is allowed along with how many requests remain. Running all of that inside one Redis EVAL call eliminates any check-then-act race condition. Both the limit and the window are caller-supplied at request time, which makes the service generic across any downstream use case without needing per-service configuration on the server.
Key Features
- Atomic, race-condition-free rate limiting via a single Redis Lua script
- Tier-aware limits — FREE users get the base limit, PREMIUM users get 3x
- Caller-supplied limit and window, making the service generic across any downstream use case
- JWT-based authentication with 7-day token expiry
- API key generation and rotation per user
- Usage visualization endpoint showing current quota consumption as a progress bar
- Three-stage Docker build with health-checked Postgres and Redis services
- Load-testing script that demonstrates real 429 responses once a quota is exceeded
Technology Stack
- Bun
- Hono
- TypeScript
- Prisma 7 (driver adapter mode)
- PostgreSQL 16
- Redis 7 + ioredis
- Zod
- Docker + Docker Compose
How Rate Limiting Works
Rate limiting is implemented as a Lua script evaluated directly by Redis. The script takes a user ID, base limit, time window, and service name; reads the tier factor from a Redis hash; computes the effective limit as baseLimit × factor; increments a counter for that user/service/window; sets its expiry on the first hit; and returns whether the request is allowed along with the remaining count. Because the entire operation runs inside one EVAL call, Redis executes it atomically — there's no window between reading the counter and writing it where two concurrent requests could both slip through.
Before the Lua script runs, the quota service always writes the current tier factor to Redis, so the multiplier used by the rate limiter stays in sync with the database even if a user's tier just changed.
| Tier | Effective limit (baseLimit = 100) |
|---|---|
| FREE | 100 per window |
| PREMIUM | 300 per window |
API Surface
POST /user/register/POST /user/login— account creation and authentication, returning a JWTGET /user/me— authenticated profile lookup (password never included in responses)POST /user/me/rotate-key— generates a new API key, replacing the old onePOST /quota/check— checks whether the authenticated user is within quota for a given service, returning200with remaining count or429when exceededGET /quota/demo— returns a text-based usage bar reading the current counter without incrementing it
Architecture
The system separates concerns cleanly: an auth middleware verifies JWTs and attaches user context; a user controller/service pair handles registration, login, and key rotation against PostgreSQL; and a quota controller/service pair syncs tier data into Redis before invoking the rate-limiting Lua script. Passwords are hashed with Bun.password (bcrypt), and the entire stack runs behind a three-stage Docker build (deps → builder → runner) with an entrypoint that applies pending Prisma migrations before starting the server.
Problems Faced
-
Race Conditions in Rate Limiting : A naive "read counter, check limit, then write" approach is vulnerable to two concurrent requests both passing the check before either writes back. Moving the entire read-increment-expire-compare sequence into a single Lua script run via
EVALremoves that race entirely, since Redis guarantees atomic execution of the script. -
Keeping Tier Data in Sync : Since tier information lives in PostgreSQL but the rate limiter only ever talks to Redis, every quota check has to push the current tier factor into Redis first — otherwise a recently upgraded or downgraded user could be rate-limited against stale data.
-
Generic, Caller-Configured Limits : Rather than hardcoding limits per service on the server, the base limit and window are supplied by the caller at request time, which keeps the service usable across many different downstream APIs without server-side reconfiguration for each one.
Key Learnings
-
Atomicity via Lua Scripting : Pushing multi-step logic into a single Redis Lua script is a clean way to get true atomicity for operations that would otherwise require explicit locking or transactions.
-
Designing for Multi-Tenancy at the Cache Layer : Keying Redis counters by user, service, and window (
hits:{userId}:{service}:{window}) made it straightforward to support arbitrary services without any schema changes. -
Observable Rate Limiting : Adding a read-only usage-visualization endpoint, separate from the actual quota-check path, made it much easier to reason about and demo current consumption without affecting the underlying counters.

