# Bun

> Step-by-step Bun rate limiting tutorial using @unkey/ratelimit. Build a Bun HTTP server that throttles abusive clients with no Redis required.

## What you'll build

A Bun HTTP server with rate limiting. Users who exceed the limit get a 429 response.

**Time to complete:** ~3 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
- [Bun](https://bun.sh) installed

<Steps titleSize="h3">

<Step title="Create a Bun project">

```bash
mkdir unkey-bun-ratelimit && cd unkey-bun-ratelimit
bun init -y
```

</Step>

<Step title="Install the SDK">

```bash
bun add @unkey/ratelimit
```

</Step>

<Step title="Add your root key">

Create a `.env` file:

```bash .env
UNKEY_ROOT_KEY="unkey_..."
```

</Step>

<Step title="Create your server">

Replace `index.ts`:

```ts index.ts
import { Ratelimit } from "@unkey/ratelimit";

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

const server = Bun.serve({
  async fetch(req) {
    const url = new URL(req.url);

    // Public route
    if (url.pathname === "/") {
      return Response.json({ message: "Welcome! Try /api/data" });
    }

    // Rate-limited route
    if (url.pathname === "/api/data") {
      // 1. Identify the user
      const identifier =
        req.headers.get("x-user-id") ??
        req.headers.get("x-forwarded-for") ??
        "anonymous";

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

      // 3. Set headers
      const headers = {
        "X-RateLimit-Limit": "10",
        "X-RateLimit-Remaining": remaining.toString(),
        "X-RateLimit-Reset": reset.toString(),
      };

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

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

    return Response.json({ error: "Not found" }, { status: 404 });
  },
  port: 3000,
});

console.log(`Server running at http://localhost:${server.port}`);
```

</Step>

<Step title="Run your server">

```bash
bun run index.ts
```

</Step>

<Step title="Test it">

```bash
# 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 return data. Requests 11+ get:

```json
{ "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                       |

## Multiple rate limiters

Create different limiters for different use cases:

```ts index.ts
import { Ratelimit } from "@unkey/ratelimit";

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

// Auth endpoints: 5/min (prevent brute force)
const authLimiter = new Ratelimit({
  rootKey: Bun.env.UNKEY_ROOT_KEY!,
  namespace: "auth",
  limit: 5,
  duration: "60s",
});

// Helper to apply rate limiting
async function checkLimit(limiter: Ratelimit, identifier: string) {
  const result = await limiter.limit(identifier);
  if (!result.success) {
    return Response.json({ error: "Rate limit exceeded" }, { status: 429 });
  }
  return null; // Allowed
}

const server = Bun.serve({
  async fetch(req) {
    const url = new URL(req.url);
    const ip = req.headers.get("x-forwarded-for") ?? "unknown";

    if (url.pathname === "/api/login") {
      const blocked = await checkLimit(authLimiter, ip);
      if (blocked) return blocked;

      return Response.json({ message: "Login endpoint" });
    }

    if (url.pathname.startsWith("/api/")) {
      const blocked = await checkLimit(apiLimiter, ip);
      if (blocked) return blocked;

      return Response.json({ message: "API data" });
    }

    return Response.json({ message: "Welcome" });
  },
  port: 3000,
});
```

## 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/bun">
    Combine with API key authentication
  </Card>
</CardGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Rate limit not applying?">
    - Verify `UNKEY_ROOT_KEY` is in your `.env` file - Use
    `Bun.env.UNKEY_ROOT_KEY` (not `process.env`) - Check your root key has
    `ratelimit.*.limit` permission
  </Accordion>

  <Accordion title="Environment not loading?">
    Bun loads `.env` automatically. Make sure the file is in your project root
    and restart the server.
  </Accordion>
</AccordionGroup>
