> ## Documentation Index
> Fetch the complete documentation index at: https://unkey.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Hono

> Step-by-step Hono rate limiting tutorial with @unkey/ratelimit. Protect routes on Node.js, Bun, or Cloudflare Workers without running Redis.

## 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

* [Unkey account](https://app.unkey.com/auth/sign-up) (free)
* [Root key](https://app.unkey.com/settings/root-keys) with `ratelimit.*.limit` permission
* Node.js 18+ or Bun

<Steps titleSize="h3">
  <Step title="Create a Hono app">
    <CodeGroup>
      ```bash npm theme={"theme":"kanagawa-wave"}
      npm create hono@latest unkey-hono-ratelimit
      cd unkey-hono-ratelimit
      ```

      ```bash pnpm theme={"theme":"kanagawa-wave"}
      pnpm create hono@latest unkey-hono-ratelimit
      cd unkey-hono-ratelimit
      ```

      ```bash bun theme={"theme":"kanagawa-wave"}
      bun create hono@latest unkey-hono-ratelimit
      cd unkey-hono-ratelimit
      ```
    </CodeGroup>

    Choose your preferred runtime.
  </Step>

  <Step title="Install the SDK">
    <CodeGroup>
      ```bash npm theme={"theme":"kanagawa-wave"}
      npm install @unkey/ratelimit
      ```

      ```bash pnpm theme={"theme":"kanagawa-wave"}
      pnpm add @unkey/ratelimit
      ```

      ```bash bun theme={"theme":"kanagawa-wave"}
      bun add @unkey/ratelimit
      ```
    </CodeGroup>
  </Step>

  <Step title="Add your root key">
    Create a `.env` file:

    ```bash .env theme={"theme":"kanagawa-wave"}
    UNKEY_ROOT_KEY="unkey_..."
    ```
  </Step>

  <Step title="Add rate limiting">
    Update `src/index.ts`:

    ```ts src/index.ts theme={"theme":"kanagawa-wave"}
    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;
    ```
  </Step>

  <Step title="Run your app">
    <CodeGroup>
      ```bash npm theme={"theme":"kanagawa-wave"}
      npm run dev
      ```

      ```bash pnpm theme={"theme":"kanagawa-wave"}
      pnpm dev
      ```

      ```bash bun theme={"theme":"kanagawa-wave"}
      bun dev
      ```
    </CodeGroup>
  </Step>

  <Step title="Test it">
    ```bash theme={"theme":"kanagawa-wave"}
    # 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:

    ```json theme={"theme":"kanagawa-wave"}
    { "error": "Too many requests. Try again later." }
    ```
  </Step>
</Steps>

## What's in the response?

`limiter.limit()` returns:

| Field       | Type      | Description                                |
| ----------- | --------- | ------------------------------------------ |
| `success`   | `boolean` | `true` if allowed, `false` if rate limited |
| `remaining` | `number`  | Requests left in current window            |
| `reset`     | `number`  | Unix timestamp (ms) when window resets     |
| `limit`     | `number`  | The configured limit                       |

## Using as middleware

Create reusable middleware for cleaner code:

```ts src/middleware/ratelimit.ts theme={"theme":"kanagawa-wave"}
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:

```ts src/index.ts theme={"theme":"kanagawa-wave"}
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

```ts theme={"theme":"kanagawa-wave"}
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:

```ts theme={"theme":"kanagawa-wave"}
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:

```bash theme={"theme":"kanagawa-wave"}
npx wrangler secret put UNKEY_ROOT_KEY
```

## Next steps

<CardGroup cols={2}>
  <Card title="How it works" icon="bolt" href="/platform/ratelimiting/how-it-works">
    Understand the architecture
  </Card>

  <Card title="Per-user overrides" icon="sliders" href="/platform/ratelimiting/overrides">
    Give specific users higher limits
  </Card>

  <Card title="SDK Reference" icon="book" href="/libraries/ts/ratelimit/ratelimit">
    All configuration options
  </Card>

  <Card title="Add API key auth" icon="key" href="/quickstart/apis/hono">
    Combine with API key authentication
  </Card>
</CardGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Rate limit not applying?">
    * 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
  </Accordion>

  <Accordion title="Environment not loading locally?">
    * For Node.js: Install `dotenv` and add `import 'dotenv/config'` - For Bun:
      `.env` loads automatically - Restart the dev server after changes
  </Accordion>
</AccordionGroup>
