Skip to main content

What you’ll build

A Hono app with rate-limited endpoints. Users who exceed the limit get a 429 response. Time to complete: ~5 minutes

Prerequisites

1

Create a Hono app

npm create hono@latest unkey-hono-ratelimit
cd unkey-hono-ratelimit
Choose your preferred runtime.
2

Install the SDK

npm install @unkey/ratelimit
3

Add your root key

Create a .env file:
.env
UNKEY_ROOT_KEY="unkey_..."
4

Add rate limiting

Update src/index.ts:
src/index.ts
import { Hono } from "hono";
import { Ratelimit } from "@unkey/ratelimit";

const app = new Hono();

// Create limiter instance
const limiter = new Ratelimit({
  rootKey: process.env.UNKEY_ROOT_KEY!,
  namespace: "hono-api",
  limit: 10,       // 10 requests...
  duration: "60s", // ...per minute
});

// Public route
app.get("/", (c) => {
  return c.json({ message: "Welcome! Try /api/data" });
});

// Rate-limited route
app.get("/api/data", async (c) => {
  // 1. Identify the user
  const identifier = c.req.header("x-user-id") 
    ?? c.req.header("x-forwarded-for")
    ?? "anonymous";

  // 2. Check the rate limit
  const { success, remaining, reset } = await limiter.limit(identifier);

  // 3. Set headers
  c.header("X-RateLimit-Limit", "10");
  c.header("X-RateLimit-Remaining", remaining.toString());
  c.header("X-RateLimit-Reset", reset.toString());

  if (!success) {
    return c.json(
      { error: "Too many requests. Try again later." },
      429
    );
  }

  // 4. Request allowed
  return c.json({ message: "Here's your data!", remaining });
});

export default app;
5

Run your app

npm run dev
6

Test it

# Hit the endpoint 12 times
for i in {1..12}; do
  curl http://localhost:3000/api/data -H "x-user-id: test-user"
  echo ""
done
First 10 requests succeed. Requests 11+ get:
{ "error": "Too many requests. Try again later." }

What’s in the response?

limiter.limit() returns:
FieldTypeDescription
successbooleantrue if allowed, false if rate limited
remainingnumberRequests left in current window
resetnumberUnix timestamp (ms) when window resets
limitnumberThe configured limit

Using as middleware

Create reusable middleware for cleaner code:
src/middleware/ratelimit.ts
import { Context, Next } from "hono";
import { Ratelimit } from "@unkey/ratelimit";

const limiter = new Ratelimit({
  rootKey: process.env.UNKEY_ROOT_KEY!,
  namespace: "api",
  limit: 100,
  duration: "60s",
});

export async function rateLimit(c: Context, next: Next) {
  const identifier = c.req.header("x-user-id") 
    ?? c.req.header("x-forwarded-for") 
    ?? "anonymous";

  const { success, remaining, reset } = await limiter.limit(identifier);

  c.header("X-RateLimit-Remaining", remaining.toString());
  c.header("X-RateLimit-Reset", reset.toString());

  if (!success) {
    return c.json({ error: "Rate limit exceeded" }, 429);
  }

  await next();
}
Apply to routes:
src/index.ts
import { Hono } from "hono";
import { rateLimit } from "./middleware/ratelimit";

const app = new Hono();

// Public
app.get("/", (c) => c.json({ message: "Welcome" }));

// Rate-limited routes
app.use("/api/*", rateLimit);

app.get("/api/data", (c) => {
  return c.json({ data: "protected content" });
});

app.get("/api/user", (c) => {
  return c.json({ user: "info" });
});

export default app;

Different limits per route group

const apiLimiter = new Ratelimit({
  rootKey: process.env.UNKEY_ROOT_KEY!,
  namespace: "api",
  limit: 100,
  duration: "60s",
});

const authLimiter = new Ratelimit({
  rootKey: process.env.UNKEY_ROOT_KEY!,
  namespace: "auth", 
  limit: 5,
  duration: "60s",
});

// Apply different limiters to different route groups
app.use("/api/*", createRateLimitMiddleware(apiLimiter));
app.use("/auth/*", createRateLimitMiddleware(authLimiter));

Deploying to Cloudflare Workers

For Cloudflare Workers, access env through the context:
app.get("/api/data", async (c) => {
  const limiter = new Ratelimit({
    rootKey: c.env.UNKEY_ROOT_KEY,  // Access from c.env
    namespace: "api",
    limit: 10,
    duration: "60s",
  });

  const { success } = await limiter.limit("user-id");
  // ...
});
Set your secret with wrangler:
npx wrangler secret put UNKEY_ROOT_KEY

Next steps

Troubleshooting

  • Verify UNKEY_ROOT_KEY is set correctly
  • For Workers: use c.env.UNKEY_ROOT_KEY, not process.env
  • Check your root key has ratelimit.*.limit permission
  • For Node.js: Install dotenv and add import 'dotenv/config'
  • For Bun: .env loads automatically
  • Restart the dev server after changes
Last modified on February 6, 2026