g14o
Packages@g14o/ratelimit

API Reference

Types for createRateLimit and rate limit tiers.

CreateRateLimitOptions

OptionTypeDescription
env?Environment | undefined

Environment name override. When omitted, falls back to process.env.NODE_ENV or "development".

Values "development" and "test" use in-memory cache and rate limiting.

inMemoryDuringBuild?boolean | undefined

When true (default), use in-memory cache/rate-limit backends during static build phases (phase-production-build, phase-export via NEXT_PHASE).

Default: true
hooks?object

Optional lifecycle hooks for observability and side effects.

Hooks are awaited; hook errors are logged and swallowed so they never affect rate limiting or fail-open behavior.

logger?object

Application logger. Defaults to a silent no-op logger.

skipRateLimit?boolean | undefined

When true, skip rate limiting for every request from this client. Evaluated at client creation — no request is available. Use per-call skipRateLimit for request-aware skip logic.

tiers?object

Override built-in tier limits/windows/prefixes. Each tier key is optional; each field inside a tier is optional and merged onto factory defaults.

redis?RedisConfig | undefined

Legacy Upstash credentials or client.

Mutually exclusive with store. Use store: upstashStore({ url, token }) for new projects.

store?object

Explicit store implementation (preferred).

RateLimitClient

PropTypeDescription
checkRateLimitfunction

Checks the rate limit for a request without wrapping a handler.

Fails open on internal errors (returns ok: true with zeroed counters).

getRateLimiterfunction

Returns a cached rate limiter adapter for a tier.

resetfunction

Clears in-memory limiter state and the adapter cache.

Useful in tests; does not reset Upstash Redis counters.

withRateLimitfunction

Wraps an async handler with rate limiting.

Returns 429 JSON with Retry-After and X-RateLimit-* headers when blocked. On success, attaches X-RateLimit-* headers to the handler response.

withUserRateLimitfunction

Wraps a handler with per-user rate limiting.

Uses getUserId(req) as the identifier; falls back to IP via getDefaultIdentifier when null.

RateLimitTierConfig

OptionTypeDescription
limit?number

Max requests allowed within window.

prefix?string

Key prefix for this tier. Default @ratelimit:<tier>.

window?Duration | undefined

Sliding window length (e.g. "60 s", "15 m").

Lifecycle hooks

Pass hooks on createRateLimit for observability and side effects. Hooks fire on the checkRateLimit path (including withRateLimit and withUserRateLimit). Skipped requests (skipRateLimit) do not fire hooks.

HookWhen it fires
onSuccessRequest allowed
onLimitExceededRequest blocked with 429
onStoreErrorStore or internal check threw (fail-open path)
onFailureUmbrella — also fires on blocked or errored outcomes; use reason ("limit_exceeded" | "store_error")
onResetreset() called

Hooks may be async. They are awaited before the response proceeds; hook errors are logged and swallowed so they never affect rate limiting or fail-open. onReset is fired from the synchronous reset() method and is not awaited by callers.

HookTypeDescription
onFailure?function

Umbrella failure hook. Also fires on blocked or errored outcomes. Use reason to distinguish limit exceeded from store errors.

onLimitExceeded?function

Fires when a request is blocked with 429.

onReset?function

Fires when RateLimitClient.reset is called.

onStoreError?function

Fires when the store or internal check throws (fail-open path).

onSuccess?function

Fires when a request is allowed.

Hook context types

TypeFields
RateLimitHookContextreq, tier, identifier, limit, remaining, reset
RateLimitStoreErrorContextreq, tier, identifier?, error
RateLimitFailureContextAbove fields plus reason: "limit_exceeded" | "store_error"
RateLimitResetContextclearedKeys

Store API

SymbolImportDescription
RateLimitStore@g14o/ratelimitStorage backend interface. Implement for custom stores.
RateLimitStoreLimiter@g14o/ratelimitTier-scoped limiter with limit(identifier).
RateLimitStoreConfig@g14o/ratelimitlimit, window, and prefix passed to createLimiter.
memoryStore@g14o/ratelimit/memoryIn-process sliding window store. Opt-in for production.
upstashStore@g14o/ratelimit/upstashUpstash Redis store (distributed). Accepts { url, token } or { redis }.
redisStore@g14o/ratelimit/redisSelf-hosted Redis store (node-redis or ioredis client). Sliding-window log via Lua.
NodeRedisLike@g14o/ratelimit/redisStructural type for node-redis clients accepted by redisStore.
IoRedisLike@g14o/ratelimit/redisStructural type for ioredis clients accepted by redisStore.
createStore@g14o/ratelimitBuild a store from atomic increment primitives (fixed-window).
defineStore@g14o/ratelimitIdentity helper for implementing RateLimitStore directly (sliding-window or custom).
StorePrimitives@g14o/ratelimitType for createStore increment/reset primitives.
import { upstashStore } from "@g14o/ratelimit/upstash";
import { memoryStore } from "@g14o/ratelimit/memory";
import { redisStore } from "@g14o/ratelimit/redis";
import { Redis } from "@upstash/redis";
import { createClient } from "redis";

// Flat credentials
const distributed = upstashStore({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

// Pre-built client
const fromEnv = upstashStore({ redis: Redis.fromEnv() });

const local = memoryStore();

// Self-hosted Redis (node-redis)
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
const tcpRedis = redisStore(redis);

Custom stores

createStore uses a fixed-window counter — suitable when your backend exposes atomic increment (Redis INCR+PEXPIRE, etc.). For sliding-window semantics, implement RateLimitStore via defineStore.

import { createStore, defineStore } from "@g14o/ratelimit";

// Fixed-window from primitives
const redisStore = createStore({
  async increment(key, windowMs) {
    const count = await redis.incr(key);
    if (count === 1) await redis.pexpire(key, windowMs);
    const ttl = await redis.pttl(key);
    return { count, reset: Date.now() + Math.max(ttl, 0) };
  },
});

// Full control (sliding-window or custom)
const customStore = defineStore({
  createLimiter(config) {
    return {
      async limit(identifier) {
        return { success: true, limit: config.limit, remaining: 9, reset: Date.now() };
      },
    };
  },
});

Store resolution when using createRateLimit:

store and redis are mutually exclusive — pass one or neither, not both. Use store: upstashStore({ url, token }) instead of combining store with the legacy redis option.

  1. options.store — explicit backend, honored in every environment including development
  2. options.redis — legacy Upstash credentials (internally creates an Upstash store)
  3. No store configured — automatic in-memory in development, test, and build phases
  4. No store in production runtime — fails open on errors; use store: memoryStore() or configure Redis

Default tier config

Built-in limits are defined in core and exposed for inspection at runtime. Values always match the default tiers table.

SymbolDescription
tokenConfigSnapshotFrozen built-in defaults per tier (limit, window, prefix).
getTokenConfigReadonly()Same data as tokenConfigSnapshot, returned as a new frozen clone.
RateLimitTierUnion of built-in tier names: "strict", "moderate", "lenient", "auth", "write".
TokenConfigResolved settings for one tier.
ReadonlyTokenConfigMapRead-only map of TokenConfig entries keyed by RateLimitTier.
import { tokenConfigSnapshot } from "@g14o/ratelimit";

console.log(tokenConfigSnapshot.auth);
// { limit: 5, window: "15 m", prefix: "@ratelimit:auth" }

Also exported from @g14o/ratelimit-nextjs, @g14o/ratelimit-express, and @g14o/ratelimit-hono.

On this page