On this page

Configuration Options

Complete reference for all hitlimit configuration options.

Basic Options

OptionTypeDefaultDescription
limitnumber100Maximum requests per window
windowstring | number'1m'Time window (e.g., '1m', '1h', '1d')
keyfunctionreq.ipFunction to extract rate limit key
storeStorememoryStore()Storage backend

Advanced Options

OptionTypeDefaultDescription
skipfunctionundefinedSkip rate limiting for certain requests
headersbooleantrueInclude rate limit headers
statusCodenumber429HTTP status when rate limited
responseobject | function-Custom response body

Ban Options

Automatically ban clients that repeatedly exceed rate limits. After the configured number of violations, the client is blocked for the ban duration without consuming store hits.

OptionTypeDefaultDescription
ban.thresholdnumber-Number of rate limit violations before triggering a ban
ban.durationstring | number-How long the ban lasts (e.g., '1h', '30m', or milliseconds)
hitlimit({
  limit: 10,
  window: '1m',
  ban: {
    threshold: 5,   // Ban after 5 violations
    duration: '1h'    // Ban lasts 1 hour
  }
})

When a client is banned, the response includes X-RateLimit-Ban: true and X-RateLimit-Ban-Expires headers, plus banned: true in the response body.

Group Options

Group multiple clients under a shared rate limit key prefix. Useful for per-API-key limits, per-tenant throttling, or per-organization quotas.

OptionTypeDefaultDescription
groupstring | functionundefinedStatic group name or function returning a group ID per request
// Static group — all keys prefixed with "api:"
hitlimit({ group: 'api', limit: 100, window: '1m' })

// Dynamic group — per API key
hitlimit({
  limit: 1000,
  window: '1h',
  group: (req) => req.headers['x-api-key'] || 'anonymous'
})

Store Configuration

hitlimit supports four storage backends. Memory is the default and fastest option:

StoreBest ForThroughput
memoryStore()Single-process, highest performance3.16M ops/s
sqliteStore()Persistence across restarts352K ops/s
redisStore()Distributed / multi-server deployments6.7K ops/s
postgresStore()Teams already using Postgres3.0K ops/s
StoreBest ForThroughput
memoryStore()Single-process, highest performance8.32M ops/s
sqliteStore()Persistence across restarts (bun:sqlite)325K ops/s
redisStore()Distributed / multi-server deployments6.7K ops/s
postgresStore()Teams already using Postgres3.7K ops/s
import { hitlimit, memoryStore } from '@joint-ops/hitlimit'
import { sqliteStore } from '@joint-ops/hitlimit/stores/sqlite'
import { redisStore } from '@joint-ops/hitlimit/stores/redis'

// Memory (default) — no config needed
hitlimit({ limit: 100, window: '1m' })

// SQLite — persistent storage
hitlimit({
  limit: 100,
  window: '1m',
  store: sqliteStore({ path: './limits.db' })
})

// Redis — distributed
hitlimit({
  limit: 100,
  window: '1m',
  store: redisStore({ url: 'redis://localhost:6379' })
})
import { hitlimit, sqliteStore, redisStore } from '@joint-ops/hitlimit-bun'

// Memory (default) — no config needed
hitlimit({ limit: 100, window: '1m' })

// SQLite — persistent storage via bun:sqlite
hitlimit({
  limit: 100,
  window: '1m',
  store: sqliteStore({ path: './limits.db' })
})

// Redis — distributed
hitlimit({
  limit: 100,
  window: '1m',
  store: redisStore({ url: 'redis://localhost:6379' })
})

Full Example

hitlimit({
  limit: 100,
  window: '1m',
  key: (req) => req.headers['x-api-key'] || req.ip,
  store: sqliteStore({ path: './limits.db' }),
  skip: (req) => req.path === '/health',
  headers: true,
  statusCode: 429,
  response: { error: 'Too many requests' },
  ban: { threshold: 5, duration: '1h' },
  group: (req) => req.headers['x-api-key'] || 'default'
})