JSPM

@ambergristle/hono-rate-limiter

0.0.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 13
  • Score
    100M100P100Q68104F
  • License MIT

Basic rate limiting for Hono, featuring a variety of algorithms

Package Exports

    Readme

    TL;DR

    A flexible rate limiting solution designed for Hono + (Upstash) Redis (for now), with four built-in algorithms.

    Modularity and interoperability are key goals. The RateLimiter can be used without the middleware, if you prefer to configure request handling yourself. I'll extend support to node-redis soon, and I'll look into implementing the algorithms using Cloudflare KV.

    The middleware was inspired by hono-rate-limiter, and the algorithms are borrowed from [`@upstash/ratelimit-js](github.com/upstash/ratelimit-js) and Lucia Auth.

    Get Started

    import { Redis } from '@upstash/redis';
    import { rateLimiter } from '@ambergristle/hono-rate-limiter';
    import { FixedWindowCounter } from '@ambergristle/hono-rate-limiter/algorithms';
    
    const globalGetLimiter = rateLimiter({
      client: Redis.fromEnv(), // don't forget to set env variables!
      algorithm: FixedWindowCounter.init(100, 60),
      cost: (c) => c.req.method === 'GET' ? 1 : 3,
      generateKey: (c) => getConnInfo(c).remote.address,
    });
    
    const app = new Hono()
      .use('*', globalGetLimiter)
      .get('/', (c) => c.text('Rate limited!'));
    
    export default app;

    Generate identifiers from Context variables

    type AuthEnv = {
      Variables: {
        user: { userId: string };
      }
    }
    
    const userLimiter = rateLimiter<AuthEnv>({
      // ...
      generateKey: (c) => c.var.user.userId,
    });
    
    

    Initialize limiter from Context

    type RedisEnv = {
      Variables: {
        client: Redis;
      }
    }
    
    rateLimiter<RedisEnv>({
      client: (c) => c.var.client,
      // ...
    });

    Make sure cache is persisted beyond requests

    const blockedCache = new Map<string, number>();
    
    rateLimiter<RedisEnv>({
      // ...
      blockedCache,
    });

    Configure response Headers and error Response

    rateLimiter<RedisEnv>({
      // ...
      headerSpec: 'draft-7', // 'draft-6' | 'draft-7' | 'draft-8'
      refundFailedRequests: true,
      errorResponse: (c) => {
        return c.text('Slow down!', 420);
      }
    });

    Customize key prefixing

    {keyPrefix?}:{policyName}:{key}

    rateLimiter<RedisEnv>({
      // ...
      keyPrefix: 'limit',
      policyName: 'global',
    });

    Roadmap

    improve tests, benchmarks

    • optimize test design to provide better coverage, use fewer resrouces
    • add coverage for algorithm-specific behaviors
    • add coverage/benchmarks for limiter performance

    reset all

    • should be able to clear redis policy-prefixed records
    • should this exist at the RateLimiter level?

    improve errors

    • distinguish between different error types?

    open questions

    • how should request cost be communicated to clients
      • is it factored into

    Future

    deny list

    • allow identifiers? to be blacklisted
    • use a reference list as a starting point?

    safe load scripts

    • look into prehashing scripts, should be ez
    • add preloading for less-likely scripts, try to load if not found

    dynamic cost window log

    • doesn't obviously make sense, poses some serious technical challenges

    dynamic refunds

    • only required if limiter is untethered from middleware

    tests

    [x] headers

    [x] cache

    middleware [] configuration (validate required arguments and outputs) [] headers [] error response

    algorithms [x] configuration (validate required arguments and outputs) [x] basic [x] returns rate limit info and limiter result' [x] blocks after limit exceeded [x] allows additional requests after reset [x] works consistently (this is a weak test) [x] rejects all requests if max=0 [] specific [] fixed doubled at boundaries [] sliding smooth at boundaries [] log exact [] bucket? burst and rate [x] check: check method returns current rate limit info [x] refund: refund method restores quota units [x] reset: reset method deletes identifier bucket

    performance [] response time [] resource use

    • concurrent requests
    • multiple users (limits isolated per id)
    • configurable windows