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 successbooleantrue if allowed, false if rate limitedremainingnumberRequests left in current window resetnumberUnix timestamp (ms) when window resets limitnumberThe 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
How it works Understand the architecture
Per-user overrides Give specific users higher limits
SDK Reference All configuration options
Add API key auth Combine with API key authentication
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: bash npm install -D typescript @types/express @types/node ts import express from "express"; import{" "} {Ratelimit} from "@unkey/ratelimit";
Last modified on March 30, 2026