HonoHub Logo

HonoHub

Introduction

Understanding data stores in hono-rate-limiter

Data stores are used by hono-rate-limiter to track the number of requests each client has made within a time window. The choice of store affects how rate limiting behaves in different deployment scenarios.

Why Use External Stores?

By default, hono-rate-limiter uses an in-memory store (MemoryStore). While simple to set up, it has limitations:

  • No state sharing: Each process/server maintains its own count
  • State loss on restart: All rate limit data is lost when the process restarts
  • Inconsistent limits: In multi-server deployments, clients can exceed limits

For production deployments, especially with multiple servers or serverless functions, use an external store like Redis or Cloudflare KV.

Available Stores

MemoryStore

The default store that keeps hit counts in memory. Suitable for development and single-process applications.

import { rateLimiter } from "hono-rate-limiter";

app.use(
  rateLimiter({
    windowMs: 60_000,
    limit: 100,
    keyGenerator: (c) => c.req.header("x-forwarded-for") ?? "",
    // MemoryStore is used by default
  })
);

Characteristics

  • Zero configuration required
  • Fast (no network calls)
  • Does not share state across processes
  • State is lost on restart
  • Suitable for development and single-instance deployments

How It Works

The MemoryStore uses two internal maps to efficiently track clients:

  1. Current window: Active clients making requests
  2. Previous window: Clients from the last window (for cleanup)

When a window expires, clients in the "previous" map are cleared, and "current" becomes "previous". This approach allows efficient memory management without iterating through all keys.

Community-Compatible Stores

Since hono-rate-limiter follows a similar store interface to express-rate-limit, many community stores are compatible:

StoreBackendPackage
rate-limit-redisRedis (ioredis)npm
rate-limit-postgresqlPostgreSQLnpm
rate-limit-memcachedMemcachednpm
typeorm-rate-limit-storeTypeORM (multiple DBs)npm
cluster-memory-storeNode.js Clusternpm

Type Compatibility

When using third-party stores, you may need to cast them to work with TypeScript. See Troubleshooting for details.

Creating a Custom Store

You can create your own store by implementing the Store interface:

import type { Store, ClientRateLimitInfo, HonoConfigType } from "hono-rate-limiter";

class MyCustomStore implements Store {
  windowMs: number = 0;

  init(options: HonoConfigType) {
    this.windowMs = options.windowMs;
  }

  async get(key: string): Promise<ClientRateLimitInfo | undefined> {
    // Fetch hit count and reset time for the key
  }

  async increment(key: string): Promise<ClientRateLimitInfo> {
    // Increment hit count and return updated info
    return { totalHits: 1, resetTime: new Date(Date.now() + this.windowMs) };
  }

  async decrement(key: string): Promise<void> {
    // Decrement hit count
  }

  async resetKey(key: string): Promise<void> {
    // Reset hit count for the key
  }

  async resetAll(): Promise<void> {
    // Reset all keys (optional)
  }

  async shutdown(): Promise<void> {
    // Cleanup resources (optional)
  }
}

Store Interface

MethodRequiredDescription
init(options)NoCalled when the middleware is initialized
get(key)NoFetch current hit count and reset time
increment(key)YesIncrement hit count, return updated info
decrement(key)YesDecrement hit count
resetKey(key)YesReset hit count for a specific key
resetAll()NoReset all keys
shutdown()NoCleanup resources (timers, connections)

ClientRateLimitInfo

The increment and get methods should return an object with:

type ClientRateLimitInfo = {
  totalHits: number;      // Current number of hits
  resetTime?: Date;       // When the window resets
};