bunlimit
bunlimit
DocumentationGitHub Introduction Installation Usage Algorithms Adapters API Reference

Adapters

Use different Redis clients with bunlimit

bunlimit supports multiple Redis clients through its adapter API. This allows you to use your preferred Redis client while maintaining the same simple rate limiting interface.

Built-in Adapters

Adapter Comparison

AdapterBest ForPerformanceFeatures
BunRedisAdapterBun projects⚡⚡⚡ FastestNative, optimized
IoRedisAdapterExisting ioredis projects⚡⚡ FastCluster, Sentinel
NodeRedisAdapterNode.js projects⚡⚡ FastOfficial client
DenoKvAdapterDeno projects⚡⚡ FastNo Redis needed
CustomSpecial requirementsVariesFull control

Custom Adapters

Create your own adapter for any Redis client or custom storage backend.

RedisAdapter Interface

interface RedisAdapter {
  incr(key: string): Promise<number>
  get(key: string): Promise<string | null>
  set(key: string, value: string): Promise<void>
  expire(key: string, seconds: number): Promise<void>
  del(...keys: string[]): Promise<void>
  keys(pattern: string): Promise<string[]>
  hincrby(key: string, field: string, increment: number): Promise<number>
  hmget(key: string, fields: string[]): Promise<(string | null)[]>
  hmset(key: string, data: string[]): Promise<void>
}

Example: Memory Adapter

import type { RedisAdapter } from 'bunlimit'

class MemoryAdapter implements RedisAdapter {
  private store = new Map<string, string>()
  private expirations = new Map<string, number>()

  async incr(key: string): Promise<number> {
    const current = parseInt(this.store.get(key) ?? '0', 10)
    const next = current + 1
    this.store.set(key, next.toString())
    return next
  }

  async get(key: string): Promise<string | null> {
    this.checkExpiration(key)
    return this.store.get(key) ?? null
  }

  async set(key: string, value: string): Promise<void> {
    this.store.set(key, value)
  }

  async expire(key: string, seconds: number): Promise<void> {
    this.expirations.set(key, Date.now() + seconds * 1000)
  }

  async del(...keys: string[]): Promise<void> {
    for (const key of keys) {
      this.store.delete(key)
      this.expirations.delete(key)
    }
  }

  async keys(pattern: string): Promise<string[]> {
    const regex = new RegExp(pattern.replace(/\*/g, '.*'))
    return Array.from(this.store.keys()).filter((key) => regex.test(key))
  }

  async hincrby(key: string, field: string, increment: number): Promise<number> {
    const hash = JSON.parse(this.store.get(key) ?? '{}')
    hash[field] = (hash[field] ?? 0) + increment
    this.store.set(key, JSON.stringify(hash))
    return hash[field]
  }

  async hmget(key: string, fields: string[]): Promise<(string | null)[]> {
    const hash = JSON.parse(this.store.get(key) ?? '{}')
    return fields.map((field) => hash[field]?.toString() ?? null)
  }

  async hmset(key: string, data: string[]): Promise<void> {
    const hash = JSON.parse(this.store.get(key) ?? '{}')
    for (let i = 0; i < data.length; i += 2) {
      hash[data[i]!] = data[i + 1]
    }
    this.store.set(key, JSON.stringify(hash))
  }

  private checkExpiration(key: string): void {
    const expiration = this.expirations.get(key)
    if (expiration && Date.now() > expiration) {
      this.store.delete(key)
      this.expirations.delete(key)
    }
  }
}

const ratelimit = new Ratelimit({
  adapter: new MemoryAdapter(),
  limiter: fixedWindow(10, 60),
})

Migration Guide

From Direct Redis to Adapter

Before:

const ratelimit = new Ratelimit({
  redis: new RedisClient(),
  limiter: fixedWindow(10, 60),
})

After (explicit adapter):

const ratelimit = new Ratelimit({
  adapter: new BunRedisAdapter(new RedisClient()),
  limiter: fixedWindow(10, 60),
})

Note: The shorthand redis option still works and automatically creates a BunRedisAdapter.

From ioredis to bunlimit

import Redis from 'ioredis'
import { Ratelimit, IoRedisAdapter, fixedWindow } from 'bunlimit'

// Your existing ioredis client
const redis = new Redis()

// Wrap it with bunlimit
const ratelimit = new Ratelimit({
  adapter: new IoRedisAdapter(redis),
  limiter: fixedWindow(100, 60),
})

// Use in your Express app
app.use(async (req, res, next) => {
  const { success } = await ratelimit.limit(req.ip)
  if (!success) {
    return res.status(429).send('Too many requests')
  }
  next()
})

Multiple Adapters

You can use multiple adapters in the same application:

// Fast local rate limiting with Bun Redis
const localLimit = new Ratelimit({
  redis: new RedisClient('redis://localhost:6379'),
  limiter: fixedWindow(100, 60),
})

// Distributed rate limiting with ioredis cluster
const distributedLimit = new Ratelimit({
  adapter: new IoRedisAdapter(clusterClient),
  limiter: slidingWindow(1000, 60),
})

// Serverless rate limiting with Deno KV
const serverlessLimit = new Ratelimit({
  adapter: new DenoKvAdapter(kv),
  limiter: tokenBucket(50, 60),
})

FAQ

Can I switch adapters without changing code?

Yes! The adapter is configured at initialization. Your rate limiting logic remains the same:

const adapter = getAdapter() // Returns any RedisAdapter

const ratelimit = new Ratelimit({
  adapter,
  limiter: fixedWindow(10, 60),
})

const { success } = await ratelimit.limit('user-123')

Do adapters affect rate limiting accuracy?

No. All adapters implement the same interface and produce identical rate limiting behavior.

Which adapter should I use?

  • Bun projects: Use BunRedisAdapter (or shorthand redis option)
  • Existing ioredis: Use IoRedisAdapter
  • Node.js with official client: Use NodeRedisAdapter
  • Deno/Serverless: Use DenoKvAdapter
  • Special needs: Create a custom adapter

Algorithms

Rate limiting algorithms explained

API Reference

Complete API documentation

On this page

Built-in AdaptersFeaturesSupported FeaturesExample with ClusterFeaturesFeaturesSelf-Hosted SetupAdapter ComparisonCustom AdaptersRedisAdapter InterfaceExample: Memory AdapterMigration GuideFrom Direct Redis to AdapterFrom ioredis to bunlimitMultiple AdaptersFAQCan I switch adapters without changing code?Do adapters affect rate limiting accuracy?Which adapter should I use?