← All @molecule/* packages · App templates
@molecule/api-rate-limitCore interface · rate-limit · API (Node) · v1.0.1 · Apache-2.0
Rate-limit core interface for molecule.dev
npm install @molecule/api-rate-limit@molecule/api-rate-limit is the rate-limit core interface on the API (Node) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 2 providers: @molecule/api-rate-limit-memory, @molecule/api-rate-limit-redis.
import { setProvider, consume, createRateLimitMiddleware } from '@molecule/api-rate-limit'
import { provider as memory } from '@molecule/api-rate-limit-memory'
setProvider(memory)
// Use convenience functions directly
const result = await consume('user:123')
if (!result.allowed) console.log('Rate limited, retry after', result.retryAfter)
// Or as Express middleware
app.use(createRateLimitMiddleware({ windowMs: 60_000, max: 100 }))Providers (2): @molecule/api-rate-limit-memory, @molecule/api-rate-limit-redis
Works with: @molecule/api-bond, @molecule/api-i18n
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Provider-agnostic rate-limiting interface for molecule.dev.
Defines the RateLimitProvider interface for request throttling with
configurable windows, token consumption, and reset. Bond packages
(in-memory, Redis, etc.) implement this interface. Application code
uses the convenience functions (check, consume, reset, getRemaining)
which delegate to the bonded provider, or the Express middleware factory.
import { setProvider, consume, createRateLimitMiddleware } from '@molecule/api-rate-limit'
import { provider as memory } from '@molecule/api-rate-limit-memory'
setProvider(memory)
// Use convenience functions directly
const result = await consume('user:123')
if (!result.allowed) console.log('Rate limited, retry after', result.retryAfter)
// Or as Express middleware
app.use(createRateLimitMiddleware({ windowMs: 60_000, max: 100 }))
core
npm install @molecule/api-rate-limit @molecule/api-bond @molecule/api-i18n
RateLimitOptionsConfiguration options for rate limiting.
interface RateLimitOptions {
/** Time window in milliseconds. */
windowMs: number
/** Maximum number of requests allowed within the window. */
max: number
/** Optional prefix for rate limit keys (useful for namespacing). */
keyPrefix?: string
/**
* If `true`, a request that ends in failure (final HTTP status `>= 400`) is not
* counted against the limit — its consumed token is refunded once the response
* completes. Honored ONLY by {@link createRateLimitMiddleware}, which observes
* the response status; a direct `consume()` call cannot know the outcome, so it
* never rolls anything back.
*/
skipFailedRequests?: boolean
/**
* If `true`, a request that ends in success (final HTTP status `< 400`) is not
* counted against the limit — its consumed token is refunded once the response
* completes. Honored ONLY by {@link createRateLimitMiddleware}; a direct
* `consume()` call cannot know the outcome.
*/
skipSuccessfulRequests?: boolean
}
RateLimitProviderRate limit provider interface.
All rate limit providers must implement this interface.
interface RateLimitProvider {
/**
* Checks whether a request identified by `key` is within the rate limit
* without consuming a token.
*
* @param key - Unique identifier for the rate limit bucket (e.g. IP, user ID).
* @returns The current rate limit state for the key.
*/
check(key: string): Promise<RateLimitResult>
/**
* Consumes one or more tokens from the rate limit bucket.
*
* @param key - Unique identifier for the rate limit bucket.
* @param cost - Number of tokens to consume (defaults to 1).
* @returns The updated rate limit state after consumption.
*/
consume(key: string, cost?: number): Promise<RateLimitResult>
/**
* Resets the rate limit state for a given key.
*
* @param key - Unique identifier for the rate limit bucket to reset.
*/
reset(key: string): Promise<void>
/**
* Returns the number of remaining tokens for a given key.
*
* @param key - Unique identifier for the rate limit bucket.
* @returns Number of remaining requests in the current window.
*/
getRemaining(key: string): Promise<number>
/**
* Refunds (un-consumes) previously consumed tokens for a key, rolling back a
* prior {@link consume}. Used by {@link createRateLimitMiddleware} to honor
* {@link RateLimitOptions.skipFailedRequests} /
* {@link RateLimitOptions.skipSuccessfulRequests}: once the response status is
* known, a request that should not count has its token rolled back.
*
* Providers refund the most recent consumption(s); the bucket never drops below
* zero, and refunding an unknown/expired bucket (or a non-positive `cost`) is a
* no-op.
*
* @param key - Unique identifier for the rate limit bucket.
* @param cost - Number of tokens to refund (defaults to 1).
*/
refund(key: string, cost?: number): Promise<void>
/**
* Applies new rate limit configuration to the provider.
*
* @param options - The rate limit options to apply.
*/
configure(options: RateLimitOptions): void
}
RateLimitResultResult of a rate limit check or consumption.
interface RateLimitResult {
/** Whether the request is allowed. */
allowed: boolean
/** Number of requests remaining in the current window. */
remaining: number
/** Total requests allowed in the window. */
total: number
/** Date when the current window resets. */
resetAt: Date
/** Seconds until the client should retry (present only when `allowed` is `false`). */
retryAfter?: number
}
RequestHandlerExpress-compatible request handler.
type RequestHandler = (req: Request, res: Response, next: NextFunction) => void | Promise<void>
check(key)Checks whether a request identified by key is within the rate limit
without consuming a token.
function check(key: string): Promise<RateLimitResult>
key — Unique identifier for the rate limit bucket (e.g. IP, user ID).Returns: The current rate limit state for the key.
configure(options)Applies new rate limit configuration to the bonded provider.
function configure(options: RateLimitOptions): void
options — The rate limit options to apply.consume(key, cost)Consumes one or more tokens from the rate limit bucket.
function consume(key: string, cost?: number): Promise<RateLimitResult>
key — Unique identifier for the rate limit bucket.cost — Number of tokens to consume (defaults to 1).Returns: The updated rate limit state after consumption.
createRateLimitMiddleware(options)Creates an Express middleware that enforces rate limiting.
When the rate limit is exceeded, responds with HTTP 429 and sets standard
rate-limit headers (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset,
Retry-After).
If options.skipFailedRequests or options.skipSuccessfulRequests is set, the
token consumed for an allowed request is refunded (via provider.refund()) once
the response completes and its final status matches — failed is statusCode >= 400,
successful is < 400 — so those requests are not counted against the limit.
function createRateLimitMiddleware(options?: RateLimitOptions): RequestHandler
options — Optional rate limit configuration to apply before the middleware runs.Returns: An Express request handler.
getProvider()Retrieves the bonded rate-limit provider, throwing if none is configured.
function getProvider(): RateLimitProvider
Returns: The bonded rate-limit provider.
getRemaining(key)Returns the number of remaining tokens for a given key.
function getRemaining(key: string): Promise<number>
key — Unique identifier for the rate limit bucket.Returns: Number of remaining requests in the current window.
hasProvider()Checks whether a rate-limit provider is currently bonded.
function hasProvider(): boolean
Returns: true if a rate-limit provider is bonded.
refund(key, cost)Refunds (un-consumes) previously consumed tokens for a key on the bonded provider, rolling back a prior {@link consume}.
function refund(key: string, cost?: number): Promise<void>
key — Unique identifier for the rate limit bucket.cost — Number of tokens to refund (defaults to 1).Returns: A promise that resolves when the tokens have been refunded.
reset(key)Resets the rate limit state for a given key.
function reset(key: string): Promise<void>
key — Unique identifier for the rate limit bucket to reset.Returns: A promise that resolves when the bucket has been reset.
setProvider(provider)Registers a rate-limit provider as the active singleton. Called by bond packages during application startup.
function setProvider(provider: RateLimitProvider): void
provider — The rate-limit provider implementation to bond.| Provider | Package |
|---|---|
| In-memory rate limit | @molecule/api-rate-limit-memory |
| Rate Limit | @molecule/api-rate-limit-redis |
Peer dependencies:
@molecule/api-bond ^1.0.1@molecule/api-i18n ^1.0.1@molecule/api-bond
@molecule/api-i18n
The memory bond is per-process. Counters reset on restart and are NOT shared across
instances — behind a load balancer each instance enforces its own budget. Use a
shared-store bond (e.g. @molecule/api-rate-limit-redis) when running more than one
process.
The middleware keys by req.ip. Behind a reverse proxy every request can carry the
proxy's address — enable your framework's proxy trust (e.g. Express
app.set('trust proxy', 1)) so req.ip is the real client, or ALL users share one
bucket.
One provider = one active config. createRateLimitMiddleware(options) re-applies its
options on every request, so stacking middlewares with different options makes them
clobber each other's window/max — and both count against the same req.ip key. Mount ONE
app-wide middleware; for a stricter limit on a sensitive endpoint (login, OTP, password
reset) call consume('login:' + identifier) directly in that handler with its own
namespaced key.
Rate-limit auth endpoints by the attempted identifier (email/username), not only IP — a credential-stuffing attacker rotates IPs but reuses identifiers.
skipFailedRequests/skipSuccessfulRequests on {@link RateLimitOptions} are honored by
createRateLimitMiddleware: after the response completes, a request whose final status
matches the flag (>= 400 for failed, < 400 for successful) has its consumed token
refunded via the provider's refund(), so it isn't counted. They apply ONLY through the
middleware — calling consume() directly cannot know the outcome and rolls nothing back.
On rejection respond 429 with Retry-After (the middleware does this and sets the
standard RateLimit-* headers from {@link RateLimitResult}).
Integration checklist — exercise the REAL behavior end-to-end (drive the protected app action in the live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
RateLimitOptions.max times succeeds, and the NEXT request
within windowMs is REJECTED with the provider's limited response (HTTP
429 + Retry-After from createRateLimitMiddleware) — not silently
allowed. THE #1 TRAP: the middleware is defined but never actually mounted
on the route (or consume(key) is never called in the handler), so nothing
is limited.consume(key) /
middleware req.ip key is per-user (or per-IP for anon), NOT one GLOBAL
bucket: two different users/IPs have INDEPENDENT budgets (user B is not
throttled by user A's traffic), and a per-user limit is not trivially
bypassed by rotating an unauthenticated path. A global bucket for a
per-user limit is both a DoS vector and a correctness bug.RateLimitResult.remaining (and getRemaining(key)) decrements
accurately with each consume and reads what's left, and
RateLimitResult.resetAt reports when the current window resets.RateLimitOptions.windowMs elapses (or
reset(key) is called) the budget is restored and the action succeeds
again — a limit is never permanent.RateLimitResult.resetAt /
retryAfter), not a blank error or a stuck spinner.consume is atomic under concurrency: N simultaneous requests cannot
all slip through above RateLimitOptions.max (no check-then-consume
race that lets the budget be exceeded).