API Reference
new Ratelimit(config)
Section titled “new Ratelimit(config)”Creates a new rate limiter instance.
import { Pool } from "pg";import { Ratelimit } from "pg-ratelimit";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const ratelimit = new Ratelimit({ pool, limiter: Ratelimit.slidingWindow(10, "1m"), prefix: "api", durable: false, debug: false,});Config:
| Property | Type | Description |
|---|---|---|
pool | pg.Pool | A pg connection pool |
limiter | Algorithm | Algorithm config from a static helper method |
prefix | string | Required - namespaces keys to prevent collisions |
durable | boolean | Optional. Default: false - true uses logged table |
synchronousCommit | boolean | Only when durable: true. Default: false - see RatelimitConfig |
debug | boolean | Optional. Default: false - logs queries to console.debug |
clock | Clock | Optional. Custom clock for testing. Defaults to () => new Date() |
cleanupProbability | number | Optional. Default: 0.1 - probability (0–1) that a limit() call runs expired-row cleanup. See Cleanup strategy |
inMemoryBlock | boolean | Optional. Default: false - cache blocked keys in-process to skip DB round trips. See In-memory blocking |
maxBlockedKeys | number | Only when inMemoryBlock: true. Default: 10000 - OOM safety cap for cached entries |
Auto-creates tables on first use via CREATE TABLE IF NOT EXISTS. Disable with PG_RATELIMIT_DISABLE_AUTO_MIGRATE=true.
ratelimit.limit(key, opts?)
Section titled “ratelimit.limit(key, opts?)”Check and consume rate limit tokens. Also performs inline cleanup of expired rows for its prefix.
const result = await ratelimit.limit("user:123");
if (!result.success) { return new Response("Too Many Requests", { status: 429, headers: { "Retry-After": String(Math.ceil((result.reset - Date.now()) / 1000)) }, });}Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
key | string | - | Identifier for the rate limit subject |
opts.rate | number | 1 | Tokens to consume. Negative values refund tokens |
Returns: Promise<LimitResult>
Since rate controls how many tokens each call consumes, you can use it for weighted costs, per-tier differentiation, or refunds.
Weighted costs
Section titled “Weighted costs”// Expensive operation costs 5 tokensawait ratelimit.limit("user:123", { rate: 5 });Refunding tokens
Section titled “Refunding tokens”// Refund after a failed uploadawait ratelimit.limit("user:123", { rate: -1 });Negative rates intentionally do not clamp to the max limit - tokens can exceed the maximum for admin/refund use cases.
Per-tier limits
Section titled “Per-tier limits”Use rate to charge different costs per tier without creating separate instances:
await ratelimit.limit("user:123", { rate: isPremium ? 1 : 5,});Error handling
Section titled “Error handling”All methods (limit, blockUntilReady, getRemaining, resetUsedTokens) throw if the database is unreachable or returns an error. The original pg error propagates directly - nothing is wrapped or swallowed.
The library does not fail open or fail closed by default. You decide:
// Fail open - allow requests when DB is downconst result = await ratelimit.limit(key).catch(() => ({ success: true, limit: 0, remaining: 0, reset: Date.now(),}));
// Fail closed - deny requests when DB is downconst result = await ratelimit.limit(key).catch(() => ({ success: false, limit: 0, remaining: 0, reset: Date.now(),}));ratelimit.blockUntilReady(key, timeout, opts?)
Section titled “ratelimit.blockUntilReady(key, timeout, opts?)”Polls limit() until success or timeout. Uses reset from failed attempts to calculate sleep duration.
const result = await ratelimit.blockUntilReady("user:123", "30s");
// Block until 5 tokens are availableconst result2 = await ratelimit.blockUntilReady("user:123", "30s", { rate: 5 });Parameters:
| Parameter | Type | Description |
|---|---|---|
key | string | Identifier for the rate limit subject |
timeout | Duration | number | Maximum time to wait |
opts.rate | number | Tokens to consume per attempt (default 1) |
Returns: Promise<LimitResult>
Returns failure immediately (without sleeping) if the time until reset exceeds the remaining timeout.
ratelimit.getRemaining(key)
Section titled “ratelimit.getRemaining(key)”Check remaining quota without consuming tokens.
const { remaining, reset } = await ratelimit.getRemaining("user:123");Returns: Promise<{ remaining: number; reset: number }>
ratelimit.resetUsedTokens(key)
Section titled “ratelimit.resetUsedTokens(key)”Full reset of a key’s quota. Useful for admin actions.
await ratelimit.resetUsedTokens("user:123");Returns: Promise<void>
TABLE_SQL
Section titled “TABLE_SQL”Exported constant containing the raw SQL for table creation. Use this if you disable auto-migration and manage schemas yourself.
import { TABLE_SQL } from "pg-ratelimit";
// Use in your migration tool of choiceawait pool.query(TABLE_SQL);Algorithm helpers
Section titled “Algorithm helpers”Ratelimit.fixedWindow(tokens, window)
Section titled “Ratelimit.fixedWindow(tokens, window)”Ratelimit.fixedWindow(10, "1m"); // 10 requests per minuteRatelimit.fixedWindow(100, "1h"); // 100 requests per hour| Parameter | Type | Description |
|---|---|---|
tokens | number | Max requests per window |
window | Duration | number | Window duration |
Ratelimit.slidingWindow(tokens, window)
Section titled “Ratelimit.slidingWindow(tokens, window)”Ratelimit.slidingWindow(50, "30s"); // 50 requests per 30 seconds| Parameter | Type | Description |
|---|---|---|
tokens | number | Max requests per window |
window | Duration | number | Window duration |
Ratelimit.tokenBucket(refillRate, interval, maxTokens)
Section titled “Ratelimit.tokenBucket(refillRate, interval, maxTokens)”Ratelimit.tokenBucket(5, "10s", 20); // Refill 5 every 10s, max 20| Parameter | Type | Description |
|---|---|---|
refillRate | number | Tokens added per interval |
interval | Duration | number | Refill interval |
maxTokens | number | Maximum bucket capacity |
LimitResult
Section titled “LimitResult”interface LimitResult { success: boolean; // Whether the request is allowed limit: number; // Max tokens allowed remaining: number; // Tokens remaining after this request reset: number; // Unix timestamp (ms) when the current window/bucket resets}RatelimitConfig
Section titled “RatelimitConfig”See new Ratelimit(config) above for all fields and defaults, and Database Design - synchronousCommit for details on how that option affects write performance and durability.
Duration
Section titled “Duration”type TimeUnit = "s" | "m" | "h" | "d";type Duration = `${number} ${TimeUnit}` | `${number}${TimeUnit}`;// '30s', '30 s', '1h', '5m', '1d'// Also accepts raw milliseconds as number where Duration | number is usedAlgorithm
Section titled “Algorithm”type Algorithm = | { type: "fixedWindow"; tokens: number; window: Duration | number } | { type: "slidingWindow"; tokens: number; window: Duration | number } | { type: "tokenBucket"; refillRate: number; interval: Duration | number; maxTokens: number };Ratelimit
Section titled “Ratelimit”class Ratelimit { constructor(config: RatelimitConfig); limit(key: string, opts?: { rate?: number }): Promise<LimitResult>; blockUntilReady( key: string, timeout: Duration | number, opts?: { rate?: number }, ): Promise<LimitResult>; getRemaining(key: string): Promise<{ remaining: number; reset: number }>; resetUsedTokens(key: string): Promise<void>;
static fixedWindow(tokens: number, window: Duration | number): Algorithm; static slidingWindow(tokens: number, window: Duration | number): Algorithm; static tokenBucket(refillRate: number, interval: Duration | number, maxTokens: number): Algorithm;}type Clock = () => Date;Used for injectable time in tests. See the Testing Guide.
In-memory blocking
Section titled “In-memory blocking”Under load, the vast majority of rate-limited requests are 429s that still make a full PostgreSQL round trip. inMemoryBlock caches blocked keys in a Map so that repeated requests from already-blocked keys return instantly without touching the database.
const ratelimit = new Ratelimit({ pool, limiter: Ratelimit.slidingWindow(100, "1m"), prefix: "api", inMemoryBlock: true,});How it works
Section titled “How it works”- When
limit()returnssuccess: false, the key and itsresettimestamp are cached in memory. - Subsequent
limit()calls for the same key return a synthetic{ success: false, remaining: 0 }result immediately - no DB query. - Once the cached
resettime passes, the entry is evicted and the next call falls through to the database normally.
Negative-rate calls (refunds) always bypass the cache and hit the database, since they may unblock a key. resetUsedTokens() also clears the cached entry for the key.
Tradeoffs
Section titled “Tradeoffs”Environment variables
Section titled “Environment variables”| Variable | Default | Description |
|---|---|---|
PG_RATELIMIT_DISABLE_AUTO_MIGRATE | false | Set to true to skip automatic table creation |
Code-level options always take precedence over environment variables.