On this page

hitlimit() Function (Bun)

Using Node.js? See the Node.js hitlimit() API for @joint-ops/hitlimit.

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

PropertyTypeDefaultDescription
limitnumber100Maximum requests per window
windowstring | 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)
storeStorememoryStore()Storage backend (memory, sqlite, redis)
skip(req) => booleanundefinedSkip rate limiting for certain requests (used by adapters)
headersboolean | HeadersConfigtrueInclude rate limit headers in adapter responses
statusCodenumber429HTTP status code when rate limited (used by adapters)
responseobject | function-Custom error response body
tiersRecord<string, TierConfig>-Named tier configurations with per-tier limit and window
tier(req) => string-Function to resolve which tier a request belongs to
banBanConfig-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):

PropertyTypeDescription
allowedbooleantrue if the request is within the rate limit
limitnumberMaximum requests allowed in the window
remainingnumberRemaining requests in the current window
resetInnumberSeconds until the window resets
resetAtnumberUnix timestamp (ms) when the window resets
bannedboolean?Present and true if the client is banned (requires ban option)

Basic Example

Rate limit a Bun.serve application using limiter.check() directly:

server.ts
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

bun-serve-adapter.ts
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

elysia-plugin.ts
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

hono-middleware.ts
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:

tiered.ts
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:

ban.ts
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:

redis.ts
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
  }
})