hitlimit() Function (Bun)
The main function to create a rate limiter for Bun applications.
Unlike the Node.js version which returns middleware, the Bun version returns a Limiter object
with a check(key) method you call directly.
import { hitlimit } from '@joint-ops/hitlimit-bun'
const limiter = hitlimit(options)
const result = await limiter.check(ip)
// result: { allowed, limit, remaining, resetIn, resetAt } Function Signature
function hitlimit(options: HitLimitOptions): Limiter
Accepts an options object and returns a Limiter instance.
The limiter does not act as middleware — instead, call limiter.check(key)
inside your request handler to perform rate limit checks.
Options Object
| Property | Type | Default | Description |
|---|---|---|---|
limit | number | 100 | Maximum requests per window |
window | string | number | '1m' | Time window duration (e.g., '1m', '1h', '1d', or milliseconds) |
key | (req) => string | - | Key extraction function (used by adapters; not needed with check(key) directly) |
store | Store | memoryStore() | Storage backend (memory, sqlite, redis) |
skip | (req) => boolean | undefined | Skip rate limiting for certain requests (used by adapters) |
headers | boolean | HeadersConfig | true | Include rate limit headers in adapter responses |
statusCode | number | 429 | HTTP status code when rate limited (used by adapters) |
response | object | function | - | Custom error response body |
tiers | Record<string, TierConfig> | - | Named tier configurations with per-tier limit and window |
tier | (req) => string | - | Function to resolve which tier a request belongs to |
ban | BanConfig | - | Auto-ban clients after repeated violations (threshold + duration) |
onStoreError | (error, req) => 'allow' | 'deny' | 'allow' | Error handler when the store fails; return 'allow' or 'deny' |
Return Value
Returns a Limiter object. The primary method is check(key), which performs
a rate limit check for the given key and returns a result object.
interface Limiter {
check(key: string): Promise<CheckResult>
}
The key parameter is typically the client's IP address, API key, or user ID.
You extract the key yourself and pass it in — this gives you full control
without framework-specific abstractions.
Check Result
The object returned by limiter.check(key):
| Property | Type | Description |
|---|---|---|
allowed | boolean | true if the request is within the rate limit |
limit | number | Maximum requests allowed in the window |
remaining | number | Remaining requests in the current window |
resetIn | number | Seconds until the window resets |
resetAt | number | Unix timestamp (ms) when the window resets |
banned | boolean? | Present and true if the client is banned (requires ban option) |
Basic Example
Rate limit a Bun.serve application using limiter.check() directly:
import { hitlimit } from '@joint-ops/hitlimit-bun'
const limiter = hitlimit({
limit: 100,
window: '1m'
})
Bun.serve({
port: 3000,
async fetch(req) {
const ip = this.requestIP(req)?.address ?? '127.0.0.1'
const result = await limiter.check(ip)
if (!result.allowed) {
return new Response(
JSON.stringify({ error: 'Too Many Requests' }),
{
status: 429,
headers: {
'Content-Type': 'application/json',
'Retry-After': String(result.resetIn)
}
}
)
}
return new Response('Hello!')
}
}) Adapter Usage
Instead of calling check() manually, you can use the built-in adapters
for Bun.serve, Elysia, and Hono. Adapters handle key extraction, headers, and error responses automatically.
Bun.serve Adapter
import { hitlimit } from '@joint-ops/hitlimit-bun/serve'
Bun.serve({
port: 3000,
async fetch(req) {
const limited = await hitlimit(req, this, {
limit: 100,
window: '1m'
})
if (limited) return limited
return new Response('Hello!')
}
}) Elysia Plugin
import { Elysia } from 'elysia'
import { hitlimit } from '@joint-ops/hitlimit-bun/elysia'
const app = new Elysia()
.use(hitlimit({ limit: 100, window: '1m' }))
.get('/', () => 'Hello!')
.listen(3000) Hono Middleware
import { Hono } from 'hono'
import { hitlimit } from '@joint-ops/hitlimit-bun/hono'
const app = new Hono()
app.use(hitlimit({ limit: 100, window: '1m' }))
app.get('/', (c) => c.text('Hello!'))
Bun.serve({ port: 3000, fetch: app.fetch }) For full adapter documentation, see Bun.serve, Elysia Plugin, and Hono Middleware.
Advanced Usage
Tiered Limits
Apply different rate limits based on user plans using the tiers and tier options:
import { hitlimit } from '@joint-ops/hitlimit-bun'
const limiter = hitlimit({
tiers: {
free: { limit: 100, window: '1h' },
pro: { limit: 1000, window: '1m' },
enterprise: { limit: 10000, window: '1m' }
},
tier: (req) => req.headers.get('x-plan') || 'free'
})
Bun.serve({
port: 3000,
async fetch(req) {
const ip = this.requestIP(req)?.address ?? '127.0.0.1'
const result = await limiter.check(ip)
if (!result.allowed) {
return new Response('Rate limit exceeded', { status: 429 })
}
return new Response('OK')
}
}) Ban Configuration
Automatically ban clients that repeatedly exceed rate limits:
import { hitlimit } from '@joint-ops/hitlimit-bun'
const limiter = hitlimit({
limit: 10,
window: '1m',
ban: {
threshold: 5, // Ban after 5 violations
duration: '1h' // Ban lasts 1 hour
}
})
Bun.serve({
port: 3000,
async fetch(req) {
const ip = this.requestIP(req)?.address ?? '127.0.0.1'
const result = await limiter.check(ip)
if (!result.allowed) {
return new Response('Too Many Requests', {
status: 429,
headers: { 'Retry-After': String(result.resetIn) }
})
}
return new Response('Hello!')
}
}) Redis Store
Use Redis for distributed rate limiting across multiple servers:
import { hitlimit, redisStore } from '@joint-ops/hitlimit-bun'
const limiter = hitlimit({
limit: 500,
window: '5m',
store: redisStore({ url: 'redis://localhost:6379' })
})
Bun.serve({
port: 3000,
async fetch(req) {
const ip = this.requestIP(req)?.address ?? '127.0.0.1'
const result = await limiter.check(ip)
if (!result.allowed) {
return new Response('Too Many Requests', { status: 429 })
}
return new Response('OK')
}
}) Store Error Handling
Control behavior when the store is unavailable:
const limiter = hitlimit({
limit: 100,
window: '1m',
store: redisStore({ url: 'redis://localhost:6379' }),
onStoreError(error, req) {
console.error('Store error:', error.message)
return 'allow' // fail open — or 'deny' to fail closed
}
})