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

# Sliding-Window Rate Limiting for Cloudflare Workers

> Add sliding-window rate limiting to your Blaze Worker using Cloudflare KV. Configure per-route limits, custom key functions, and response headers.

The `rateLimit()` middleware uses **Cloudflare KV** to implement a sliding-window counter at the edge. Because KV is globally distributed, every Cloudflare datacenter enforces the same limit against the same shared counter — making this approach well-suited to API rate limiting where consistency matters more than sub-millisecond precision.

```typescript theme={null}
import { rateLimit } from 'blaze/middleware/rate-limit'
```

<Tip>
  The KV write that increments the counter runs via `req.ctx.waitUntil()` — it happens after the response is sent and does not block your handler. This keeps the hot path as fast as a single KV read.
</Tip>

## Setup

First, declare a KV namespace binding in your `wrangler.toml`:

```toml theme={null}
[[kv_namespaces]]
binding = "RATE_LIMIT_KV"
id      = "your-kv-namespace-id"
```

Then include it in your `Env` type so `req.env.RATE_LIMIT_KV` is typed correctly:

```typescript theme={null}
// src/types/env.ts
export type Env = {
  RATE_LIMIT_KV: KVNamespace
  // … other bindings
}
```

## Basic usage

```typescript theme={null}
import { createApp } from 'blaze'
import { rateLimit } from 'blaze/middleware/rate-limit'

type Env = { RATE_LIMIT_KV: KVNamespace }

const app = createApp<Env>()

// Allow 100 requests per 60-second window, keyed by client IP
app.use(
  rateLimit({
    kvBinding: (req) => req.env.RATE_LIMIT_KV,
    limit: 100,
    window: 60,
  }),
)

app.get('/api/data', (req, res) => {
  res.json({ ok: true })
})

export default { fetch: app.fetch }
```

## `RateLimitOptions` reference

<ParamField body="kvBinding" type="string | (req) => KVNamespace" required>
  The KV namespace to use as the counter store. Pass a resolver function (`(req) => req.env.RATE_LIMIT_KV`) to resolve it from `req.env`, or pass the binding name as a string (`'RATE_LIMIT_KV'`) and Blaze resolves it automatically.
</ParamField>

<ParamField body="limit" type="number" required>
  Maximum number of requests allowed per `window` period. Requests beyond this limit receive a `429 Too Many Requests` response.
</ParamField>

<ParamField body="window" type="number" required>
  Window size in seconds. The counter resets at the start of each window. For example, `window: 60` creates per-minute buckets.
</ParamField>

<ParamField body="keyFn" type="(req) => string">
  Function that derives the rate-limit key from the request. Defaults to `req.ip` (the `CF-Connecting-IP` header). Use this to implement per-user, per-token, or per-route-and-IP limits.
</ParamField>

<ParamField body="message" type="string" default="'Too Many Requests'">
  The error message returned in the `429` JSON response body.
</ParamField>

## Custom key functions

The default key is the client IP address. Override `keyFn` to implement per-user or per-API-key limits:

<CodeGroup>
  ```typescript Per authenticated user theme={null}
  app.use(
    '/api',
    jwtAuth({ secret: (req) => req.env.JWT_SECRET }),
  )

  app.use(
    '/api',
    rateLimit({
      kvBinding: (req) => req.env.RATE_LIMIT_KV,
      limit: 1000,
      window: 3600, // 1,000 requests per hour per user
      keyFn: (req) => {
        // req.user is set by jwtAuth — fall back to IP for unauthenticated requests
        return req.user?.sub ?? req.ip ?? 'anonymous'
      },
    }),
  )
  ```

  ```typescript Per API key theme={null}
  app.use(
    '/api',
    rateLimit({
      kvBinding: (req) => req.env.RATE_LIMIT_KV,
      limit: 500,
      window: 60,
      keyFn: (req) => {
        // Use the Bearer token as the key so each API key gets its own bucket
        const auth = req.header('Authorization') ?? ''
        return auth.startsWith('Bearer ') ? auth.slice(7) : req.ip ?? 'unknown'
      },
    }),
  )
  ```
</CodeGroup>

## Per-route limits

Apply `rateLimit()` directly on individual routes to enforce different limits for different endpoints:

```typescript theme={null}
import { createApp } from 'blaze'
import { rateLimit } from 'blaze/middleware/rate-limit'

type Env = { RATE_LIMIT_KV: KVNamespace }
const app = createApp<Env>()

const rl = (limit: number, window: number) =>
  rateLimit({
    kvBinding: (req) => req.env.RATE_LIMIT_KV,
    limit,
    window,
    keyFn: (req) => req.ip ?? 'unknown',
  })

// Generous limit for read endpoints
app.get('/api/products', rl(300, 60), listProducts)

// Stricter limit for expensive write endpoints
app.post('/api/orders', rl(20, 60), createOrder)

// Very tight limit for authentication endpoints
app.post('/auth/login', rl(5, 60), handleLogin)
```

## Response headers

Every response — whether allowed or blocked — includes rate-limit headers so clients can track their usage and implement backoff:

| Header                  | Description                                                         |
| ----------------------- | ------------------------------------------------------------------- |
| `X-RateLimit-Limit`     | The maximum number of requests allowed per window.                  |
| `X-RateLimit-Remaining` | Requests remaining in the current window.                           |
| `X-RateLimit-Reset`     | Unix timestamp (seconds) when the current window resets.            |
| `Retry-After`           | Seconds until the window resets. **Only present on 429 responses.** |

A client hitting the limit sees:

```
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1720000060
Retry-After: 23
Content-Type: application/json

{"error":"Too Many Requests"}
```

A client within the limit sees:

```
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1720000060
```

Clients can use `X-RateLimit-Remaining` to slow down proactively before hitting the limit, and `Retry-After` to implement automatic retry logic on 429 responses.
