# Express

> Step-by-step Express.js rate limiting tutorial with @unkey/ratelimit. Set per-route limits, return 429 responses, and skip the Redis dependency.

## What you'll build

An Express server 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+

<Steps titleSize="h3">

<Step title="Create your Express app">

```bash
mkdir unkey-express-ratelimit && cd unkey-express-ratelimit
npm init -y
npm install express @unkey/ratelimit dotenv
```

</Step>

<Step title="Add your root key">

Create a `.env` file:

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

<Warning>Never commit your `.env` file. Add it to `.gitignore`.</Warning>

</Step>

<Step title="Create your server">

Create `index.js`:

```js index.js
const express = require("express");
const { Ratelimit } = require("@unkey/ratelimit");
require("dotenv").config();

const app = express();
const port = process.env.PORT || 3000;

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

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

// Rate-limited route
app.get("/api/data", async (req, res) => {
  // 1. Identify the user (IP, user ID, API key, etc.)
  const identifier = req.headers["x-user-id"] || req.ip || "anonymous";

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

  // 3. Set rate limit headers
  res.set({
    "X-RateLimit-Limit": "10",
    "X-RateLimit-Remaining": remaining.toString(),
    "X-RateLimit-Reset": reset.toString(),
  });

  if (!success) {
    return res.status(429).json({
      error: "Too many requests. Please try again later.",
      retryAfter: Math.ceil((reset - Date.now()) / 1000),
    });
  }

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

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});
```

</Step>

<Step title="Run your server">

```bash
node index.js
```

</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 succeed. Requests 11+ get:

```json
{
  "error": "Too many requests. Please try again later.",
  "retryAfter": 45
}
```

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

For cleaner code, create reusable middleware:

```js middleware/ratelimit.js
const { Ratelimit } = require("@unkey/ratelimit");

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

async function rateLimit(req, res, next) {
  const identifier = req.headers["x-user-id"] || req.ip || "anonymous";
  const { success, remaining, reset } = await limiter.limit(identifier);

  res.set({
    "X-RateLimit-Remaining": remaining.toString(),
    "X-RateLimit-Reset": reset.toString(),
  });

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

  next();
}

module.exports = { rateLimit };
```

### createMiddleware helper

Create an Express middleware from a Ratelimit instance:

```js
function createMiddleware(limiter) {
  return async (req, res, next) => {
    const identifier = req.ip ?? req.headers["x-forwarded-for"] ?? "unknown";
    const { success, remaining, reset } = await limiter.limit(identifier);

    if (!success) {
      res.set("Retry-After", Math.ceil((reset - Date.now()) / 1000).toString());
      res.set("X-RateLimit-Remaining", "0");
      return res.status(429).json({ error: "Too many requests" });
    }

    res.set("X-RateLimit-Remaining", remaining.toString());
    next();
  };
}
```

Use on any route:

```js
const { rateLimit } = require("./middleware/ratelimit");

app.get("/api/data", rateLimit, (req, res) => {
  res.json({ data: "protected content" });
});

app.post("/api/submit", rateLimit, (req, res) => {
  res.json({ success: true });
});
```

## Different limits per route

Create multiple limiters:

```js
const apiLimiter = new Ratelimit({
  rootKey: process.env.UNKEY_ROOT_KEY,
  namespace: "api",
  limit: 100,
  duration: "1m",
});

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

// 100/min for general API
app.use("/api", createMiddleware(apiLimiter));

// 5/min for login (prevent brute force)
app.post("/login", createMiddleware(authLimiter), loginHandler);
```

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

## Troubleshooting

<AccordionGroup>
  <Accordion title="Rate limit not applying?">
    - Verify `UNKEY_ROOT_KEY` is set and has `ratelimit.*.limit` permission -
    Make sure you're using a consistent identifier per user - Check that `.env`
    is loaded before creating the limiter
  </Accordion>

  <Accordion title="TypeScript version?">
    Install types and use imports:

    ```bash
    npm install -D typescript @types/express @types/node
    ```

    ```ts
    import express from "express";
    import { Ratelimit } from "@unkey/ratelimit";
    ```
  </Accordion>
</AccordionGroup>
