bunlimit
bunlimit
DocumentationGitHub Introduction Installation Usage Algorithms Adapters API Reference

Usage

Common usage patterns and examples

Basic Usage

Rate Limiting a User

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

if (!success) {
  throw new Error(`Rate limit exceeded. Try again at ${new Date(reset)}`)
}

console.log(`${remaining} requests remaining`)

HTTP Server Integration

import { RedisClient } from 'bun'
import { Ratelimit, slidingWindow } from 'bunlimit'

const redis = new RedisClient()
const ratelimit = new Ratelimit({
  redis,
  limiter: slidingWindow(100, 60),
})

Bun.serve({
  async fetch(req) {
    const ip = req.headers.get('x-forwarded-for') ?? 'unknown'
    const { success, remaining, reset } = await ratelimit.limit(ip)

    if (!success) {
      return new Response('Rate limit exceeded', {
        status: 429,
        headers: {
          'X-RateLimit-Limit': '100',
          'X-RateLimit-Remaining': '0',
          'X-RateLimit-Reset': reset.toString(),
        },
      })
    }

    return new Response('Hello World', {
      headers: {
        'X-RateLimit-Limit': '100',
        'X-RateLimit-Remaining': remaining.toString(),
        'X-RateLimit-Reset': reset.toString(),
      },
    })
  },
})

Advanced Features

Multiple Identifiers

Rate limit multiple users at once:

const results = await ratelimit.multiLimit(['user-1', 'user-2', 'user-3'])

for (const result of results) {
  console.log(`${result.identifier}: ${result.success ? 'allowed' : 'denied'}`)
}

Analytics

Track allowed and denied requests:

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

await ratelimit.limit('user-123')

const stats = await ratelimit.getAnalytics('user-123')
console.log(`Allowed: ${stats?.allowed}, Denied: ${stats?.denied}`)

Reset Rate Limit

Manually reset a user's rate limit:

await ratelimit.reset('user-123')

Check Remaining Requests

Get remaining requests without consuming one:

const remaining = await ratelimit.getRemaining('user-123')
console.log(`${remaining} requests remaining`)

Handle Rate Limit Exceeded

Execute custom logic when limits are exceeded:

const ratelimit = new Ratelimit({
  redis,
  limiter: fixedWindow(10, 60),
  onLimitExceeded: async (identifier, response) => {
    console.log(`Rate limit exceeded for ${identifier}`)

    // Send notification
    await sendEmail(identifier, {
      subject: 'Rate Limit Exceeded',
      body: `Try again at ${new Date(response.reset)}`,
    })

    // Log to analytics
    await analytics.track('rate_limit_exceeded', {
      user: identifier,
      reset: response.reset,
    })
  },
})

Common Patterns

API Key Rate Limiting

const apiLimit = new Ratelimit({
  redis,
  limiter: slidingWindow(1000, 3600), // 1000 req/hour
  prefix: 'api',
})

async function handleRequest(apiKey: string) {
  const { success, remaining } = await apiLimit.limit(apiKey)

  if (!success) {
    throw new Error('API rate limit exceeded')
  }

  return { remaining }
}

Login Attempt Limiting

const loginLimit = new Ratelimit({
  redis,
  limiter: fixedWindow(5, 300), // 5 attempts per 5 minutes
  prefix: 'login',
  onLimitExceeded: async (identifier) => {
    await logSuspiciousActivity(identifier)
  },
})

async function handleLogin(username: string, password: string) {
  const { success } = await loginLimit.limit(username)

  if (!success) {
    throw new Error('Too many login attempts. Please try again later.')
  }

  // Proceed with login
}

File Upload Limiting

const uploadLimit = new Ratelimit({
  redis,
  limiter: tokenBucket(10, 3600, 0.1), // 10 uploads, slow refill
  prefix: 'upload',
})

async function handleUpload(userId: string, file: File) {
  const { success, remaining } = await uploadLimit.limit(userId)

  if (!success) {
    throw new Error('Upload limit exceeded')
  }

  console.log(`${remaining} uploads remaining`)
  // Process upload
}

Per-Route Rate Limiting

const routes = {
  search: new Ratelimit({
    redis,
    limiter: slidingWindow(100, 60), // 100/min
    prefix: 'route:search',
  }),

  create: new Ratelimit({
    redis,
    limiter: fixedWindow(10, 60), // 10/min
    prefix: 'route:create',
  }),

  delete: new Ratelimit({
    redis,
    limiter: fixedWindow(5, 60), // 5/min
    prefix: 'route:delete',
  }),
}

async function handleRoute(route: string, userId: string) {
  const limiter = routes[route]
  const { success } = await limiter.limit(userId)

  if (!success) {
    throw new Error(`Rate limit exceeded for ${route}`)
  }
}

Tiered Rate Limiting

const tiers = {
  free: new Ratelimit({
    redis,
    limiter: fixedWindow(100, 3600),
    prefix: 'tier:free',
  }),

  pro: new Ratelimit({
    redis,
    limiter: fixedWindow(1000, 3600),
    prefix: 'tier:pro',
  }),

  enterprise: new Ratelimit({
    redis,
    limiter: fixedWindow(10000, 3600),
    prefix: 'tier:enterprise',
  }),
}

async function handleRequest(userId: string, tier: 'free' | 'pro' | 'enterprise') {
  const limiter = tiers[tier]
  const { success, remaining } = await limiter.limit(userId)

  return { success, remaining }
}

IP-Based Rate Limiting with User Override

const ipLimit = new Ratelimit({
  redis,
  limiter: fixedWindow(100, 60),
  prefix: 'ip',
})

const userLimit = new Ratelimit({
  redis,
  limiter: slidingWindow(1000, 60),
  prefix: 'user',
})

async function checkRateLimit(ip: string, userId?: string) {
  // Check IP limit first
  const ipResult = await ipLimit.limit(ip)
  if (!ipResult.success) {
    return { success: false, reason: 'IP rate limit exceeded' }
  }

  // If authenticated, check user limit
  if (userId) {
    const userResult = await userLimit.limit(userId)
    if (!userResult.success) {
      return { success: false, reason: 'User rate limit exceeded' }
    }
  }

  return { success: true }
}

Best Practices

1. Use Appropriate Prefixes

Always use descriptive prefixes to avoid key collisions:

const ratelimit = new Ratelimit({
  redis,
  limiter: fixedWindow(10, 60),
  prefix: 'api:v1:users', // Clear and specific
})

2. Return Rate Limit Headers

Help clients understand their limits:

return new Response(data, {
  headers: {
    'X-RateLimit-Limit': limit.toString(),
    'X-RateLimit-Remaining': remaining.toString(),
    'X-RateLimit-Reset': reset.toString(),
  },
})

3. Handle Errors Gracefully

try {
  const { success } = await ratelimit.limit(userId)
  if (!success) {
    return errorResponse(429, 'Rate limit exceeded')
  }
} catch (error) {
  // Log error but don't block request
  console.error('Rate limit check failed:', error)
  // Optionally allow request to proceed
}

4. Use Analytics for Monitoring

const ratelimit = new Ratelimit({
  redis,
  limiter: slidingWindow(100, 60),
  analytics: true,
})

// Periodically check analytics
setInterval(async () => {
  const stats = await ratelimit.getAnalytics('api-key-123')
  if (stats && stats.denied > 100) {
    await alertTeam('High rate limit denials detected')
  }
}, 60000)

5. Test Your Limits

// Test rate limiting in development
if (process.env.NODE_ENV === 'development') {
  const results = []
  for (let i = 0; i < 12; i++) {
    const result = await ratelimit.limit('test-user')
    results.push(result)
  }
  console.log('Rate limit test:', results)
}

Installation

Get started with bunlimit

Algorithms

Rate limiting algorithms explained

On this page

Basic UsageRate Limiting a UserHTTP Server IntegrationAdvanced FeaturesMultiple IdentifiersAnalyticsReset Rate LimitCheck Remaining RequestsHandle Rate Limit ExceededCommon PatternsAPI Key Rate LimitingLogin Attempt LimitingFile Upload LimitingPer-Route Rate LimitingTiered Rate LimitingIP-Based Rate Limiting with User OverrideBest Practices1. Use Appropriate Prefixes2. Return Rate Limit Headers3. Handle Errors Gracefully4. Use Analytics for Monitoring5. Test Your Limits