g14o
Packages@g14o/ratelimit-hono

Setup

Hono middleware, route wrappers, and per-user rate limiting.

Typing Bindings and Variables

Pass your app's Hono Env to createRateLimit so Context in middleware and handler callbacks includes typed Bindings and Variables:

src/types.ts
export interface Bindings {
  TOKEN: string;
}

export interface Variables {
  user: User;
}

export type AppEnv = { Bindings: Bindings; Variables: Variables };
lib/ratelimit.ts
import { createRateLimit } from "@g14o/ratelimit-hono";
import { env } from "./env";
import type { AppEnv } from "../types";

export const {
  middleware,
  withRateLimit,
  withUserRateLimit,
  userMiddleware,
  checkRateLimit,
} = createRateLimit<AppEnv>({
  redis: {
    url: env.UPSTASH_REDIS_REST_URL,
    token: env.UPSTASH_REDIS_REST_TOKEN,
  },
});

Bind AppEnv once at the factory. All exported helpers (withRateLimit, middleware, identifierFn callbacks, etc.) use Context<AppEnv>.

lib/ratelimit.ts
import { createRateLimit } from "@g14o/ratelimit-hono";
import { upstashStore } from "@g14o/ratelimit-hono/upstash";

export const { middleware, withRateLimit } = createRateLimit<AppEnv>({
  store: upstashStore({
    url: env.UPSTASH_REDIS_REST_URL,
    token: env.UPSTASH_REDIS_REST_TOKEN,
  }),
});

Store helpers (memoryStore, upstashStore, createStore, defineStore) are exported from @g14o/ratelimit-hono and its /memory and /upstash subpaths. store and redis are mutually exclusive.

Create a client

Same as above — export the helpers you need from a single lib/ratelimit.ts module.

Route middleware (idiomatic Hono)

Built-in tier limits are listed in @g14o/ratelimit setup — default tiers.

src/server.ts
import { Hono } from "hono";
import { middleware } from "./lib/ratelimit";
import type { AppEnv } from "./types";

const app = new Hono<AppEnv>();

app.get(
  "/api/status",
  middleware({ tier: "lenient" }),
  (c) => c.json({ ok: true })
);

Wrapped route handler

withRateLimit returns a MiddlewareHandler assignable to typed app.* routes. Handler callbacks receive your app's Context:

src/server.ts
import { withRateLimit } from "./lib/ratelimit";

app.post(
  "/api/chat",
  withRateLimit(
    (c) => {
      const user = c.get("user");
      const token = c.env.TOKEN;
      return c.json({ ok: true, user, token });
    },
    { tier: "moderate", prefix: "@ratelimit:chat" }
  )
);

Works the same in route modules — import withRateLimit from lib/ratelimit.ts.

Per-user limits

Use a verified identity from upstream auth middleware (session, JWT, etc.) — set on Variables.user, not client-controlled headers.

src/routes/user-action.ts
import { Hono } from "hono";
import { userMiddleware } from "../lib/ratelimit";
import type { AppEnv } from "../types";

const app = new Hono<AppEnv>();

app.post(
  "/api/user-action",
  userMiddleware(async (c) => c.get("user")?.id ?? null, {
    tier: "auth",
  }),
  (c) => c.json({ ok: true })
);

Manual check

Use checkRateLimit when you need full control over the response:

src/routes/custom.ts
import { checkRateLimit } from "../lib/ratelimit";
import type { Context } from "../types";

export async function customHandler(c: Context) {
  const result = await checkRateLimit(c, { tier: "strict" });
  if (!result.ok) {
    return c.json({ error: "Too many requests" }, 429);
  }
  return c.json({ ok: true });
}

On this page