Stores Overview
Stores are the backend that hitlimit uses to track request counts. Choose the right store based on your deployment needs.
Available Stores
| Store | Persistence | Multi-instance | Best For |
|---|---|---|---|
| Memory | No | No | Development, single instance |
| SQLite | Yes | No | Single server, persistence needed |
| Redis | Yes | Yes | Distributed systems, high traffic |
| Valkey | Yes | Yes | Distributed, open-source Redis alternative |
| DragonflyDB | Yes | Yes | High-throughput distributed |
| Postgres | Yes | Yes | Distributed, existing Postgres infra |
| MongoDB | Yes | Yes | Distributed, NoSQL / MEAN/MERN stacks |
| MySQL | Yes | Yes | Distributed, LAMP stacks / MariaDB |
hitlimit-bun is memory-first by default with 7.73M+ ops/sec performance. Optional persistence available with native bun:sqlite (372K ops/sec), Redis, Postgres, or MongoDB when you need distributed rate limiting.
| Store | Persistence | Multi-instance | Best For |
|---|---|---|---|
| Memory | No | No | Default — 7.73M+ ops/sec |
| SQLite | Yes | No | bun:sqlite, zero deps — 372K ops/sec |
| Redis | Yes | Yes | Distributed (network-bound) |
| Valkey | Yes | Yes | Distributed, open-source — same as Redis |
| DragonflyDB | Yes | Yes | High-throughput distributed |
| Postgres | Yes | Yes | Distributed SQL (network-bound) |
| MongoDB | Yes | Yes | Distributed NoSQL |
| MySQL | Yes | Yes | Distributed SQL — MySQL/MariaDB |
Quick Comparison
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' })
})
// Valkey - open-source Redis alternative
import { valkeyStore } from '@joint-ops/hitlimit/stores/valkey'
hitlimit({ limit: 100, window: '1m', store: valkeyStore({ url: 'redis://localhost:6379' }) })
// DragonflyDB - high-throughput Redis alternative
import { dragonflyStore } from '@joint-ops/hitlimit/stores/dragonfly'
hitlimit({ limit: 100, window: '1m', store: dragonflyStore({ 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 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' })
})
// Valkey - open-source Redis alternative
import { valkeyStore } from '@joint-ops/hitlimit-bun/stores/valkey'
hitlimit({ limit: 100, window: '1m', store: valkeyStore({ url: 'redis://localhost:6379' }) })
// DragonflyDB - high-throughput Redis alternative
import { dragonflyStore } from '@joint-ops/hitlimit-bun/stores/dragonfly'
hitlimit({ limit: 100, window: '1m', store: dragonflyStore({ url: 'redis://localhost:6379' }) }) Choosing a Store
- Development: Use the default memory store for simplicity
- Single server production: SQLite provides persistence without external dependencies
- Multiple servers: Redis ensures consistent rate limiting across all instances
- Multiple servers (open-source): Valkey provides BSD-licensed distributed rate limiting
- High-throughput distributed: DragonflyDB's multi-threaded architecture for maximum throughput
- Existing MongoDB: MongoDB store for MEAN/MERN stacks with TTL index auto-cleanup
- Existing MySQL: MySQL store for LAMP stacks with InnoDB transactions
- Custom requirements: Create your own store
Store Interface
All stores implement the same interface:
interface Store {
increment(key: string, window: number): Promise<{
count: number
resetAt: number
}>
reset(key: string): Promise<void>
} Migrating Between Stores
Swapping stores requires changing only the store option. Your application code stays the same:
// Stage 1: Start with memory (fastest, no deps)
const store = memoryStore()
// Stage 2: Add persistence (survives restarts)
// const store = sqliteStore({ path: './data/rate-limits.db' })
// Stage 3: Go distributed (shared state across instances)
// const store = redisStore({ url: 'redis://localhost:6379' })
const limiter = hitlimit({
limit: 100,
window: '1m',
store
}) All eight stores implement the same interface. No changes to your route handlers, middleware, or rate limit logic.