Skip to main content

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

1

Create your Express app

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

Add your root key

Create a .env file:
.env
UNKEY_ROOT_KEY="unkey_..."
Never commit your .env file. Add it to .gitignore.
3

Create your server

Create index.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}`);
});
4

Run your server

node index.js
5

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. Please try again later.",
  "retryAfter": 45
}

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

For cleaner code, create reusable middleware:
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:
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:
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:
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

Troubleshooting

  • 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
Install types and use imports:
npm install -D typescript @types/express @types/node
import express from "express";
import { Ratelimit } from "@unkey/ratelimit";
Last modified on February 6, 2026