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
Create your Express app
mkdir unkey-express-ratelimit && cd unkey-express-ratelimit
npm init -y
npm install express @unkey/ratelimit dotenv
Add your root key
Create a .env file:UNKEY_ROOT_KEY="unkey_..."
Never commit your .env file. Add it to .gitignore.
Create your server
Create 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}`);
});
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:
| 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:
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