@talosjs/rate-limit component throttles requests per key, where a key is an IP, a user ID, an API key, or anything else you can name. RedisRateLimiter and UpstashRedisRateLimiter both implement the IRateLimiter interface (check, isLimited, reset, getCount), so the limit can be shared across every instance of your app. Every check returns a structured result with the remaining quota, the total limit, and the exact time the window resets.
How throttling works here
RedisRateLimiter runs on Bun’s native Redis client and UpstashRedisRateLimiter runs on Upstash’s REST API; both implement IRateLimiter. You pass any string to check() (ip:1.2.3.4, user:123, apikey:abc) and each key is throttled on its own. The result carries limited, remaining, total, and resetAt, which is enough to build a Retry-After header and report the remaining quota to clients.
Counters live in Redis, so the limit holds across every instance of your app. Both limiters are registered with @talosjs/container and inject AppEnv, so they wire themselves from environment variables. When something fails, the limiter throws RateLimitException, which carries HTTP 429 Too Many Requests and a machine-readable key.
How it works
You resolve a limiter and callcheck(key) on every request you want to throttle. The backend increments a counter for that key and returns the current state. When limited is true, the caller has exceeded its quota for the current window.
The
RateLimitResultType returned by check():
The backend’s window algorithm:
RedisRateLimiter namespaces every key as <namespace>:<key> (default namespace ratelimit) so multiple apps can share one Redis instance without colliding.
Environment variables
Usage
Resolve a limiter from the container (it reads its connection details from the environment), then callcheck() on each request you want to throttle.
isLimited() when you only need the boolean, and reset() / getCount() for inspection and administration:
Decorator and usage
@decorator.rateLimit()
Registers a rate limiter class with the container. It accepts an optional scope (defaults to singleton). The packaged limiters are already decorated; use it to register a custom limiter that implements IRateLimiter, or to extend a packaged one under your own name.
Exceptions
The component throwsRateLimitException when a backend is misconfigured or an operation fails. It extends the base Exception, carries the HTTP status 429 Too Many Requests, and exposes a machine-readable key, a human-readable message, and a data object.
Throttling in practice
Pick a stable key scheme. Throttling byip:, user:, or apikey: prefixes limits each identity on its own and lets reset() target a single entry. Derive your Retry-After header from result.resetAt so clients back off for the right amount of time instead of hammering the endpoint. Reach for isLimited() when you only need to allow or deny, and the full check() result when you want to report remaining quota to the caller.
Decide up front whether you fail open or closed: wrap check() in try/catch and pick deliberate behavior for when Redis is unreachable, since rejecting all traffic on a backend outage may be worse than letting it through. When several services share one Redis, give each a distinct namespace so counters never collide. And prefer container.get() or @inject over new, so configuration flows from AppEnv and the limiter stays a singleton.