What you’ll build
A Bun HTTP server with rate limiting. Users who exceed the limit get a 429 response.
Time to complete: ~3 minutes
Prerequisites
Create a Bun project
mkdir unkey-bun-ratelimit && cd unkey-bun-ratelimit
bun init -y
Add your root key
Create a .env file:UNKEY_ROOT_KEY="unkey_..."
Create your server
Replace index.ts:import { Ratelimit } from "@unkey/ratelimit";
// Create limiter instance
const limiter = new Ratelimit({
rootKey: Bun.env.UNKEY_ROOT_KEY!,
namespace: "bun-api",
limit: 10, // 10 requests...
duration: "60s", // ...per minute
});
const server = Bun.serve({
async fetch(req) {
const url = new URL(req.url);
// Public route
if (url.pathname === "/") {
return Response.json({ message: "Welcome! Try /api/data" });
}
// Rate-limited route
if (url.pathname === "/api/data") {
// 1. Identify the user
const identifier = req.headers.get("x-user-id")
?? req.headers.get("x-forwarded-for")
?? "anonymous";
// 2. Check the rate limit
const { success, remaining, reset } = await limiter.limit(identifier);
// 3. Set headers
const headers = {
"X-RateLimit-Limit": "10",
"X-RateLimit-Remaining": remaining.toString(),
"X-RateLimit-Reset": reset.toString(),
};
if (!success) {
return Response.json(
{ error: "Too many requests. Try again later." },
{ status: 429, headers }
);
}
// 4. Request allowed
return Response.json(
{ message: "Here's your data!", remaining },
{ headers }
);
}
return Response.json({ error: "Not found" }, { status: 404 });
},
port: 3000,
});
console.log(`Server running at http://localhost:${server.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 return data. Requests 11+ get:{ "error": "Too many requests. Try again later." }
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 |
Multiple rate limiters
Create different limiters for different use cases:
import { Ratelimit } from "@unkey/ratelimit";
// General API: 100/min
const apiLimiter = new Ratelimit({
rootKey: Bun.env.UNKEY_ROOT_KEY!,
namespace: "api",
limit: 100,
duration: "60s",
});
// Auth endpoints: 5/min (prevent brute force)
const authLimiter = new Ratelimit({
rootKey: Bun.env.UNKEY_ROOT_KEY!,
namespace: "auth",
limit: 5,
duration: "60s",
});
// Helper to apply rate limiting
async function checkLimit(limiter: Ratelimit, identifier: string) {
const result = await limiter.limit(identifier);
if (!result.success) {
return Response.json(
{ error: "Rate limit exceeded" },
{ status: 429 }
);
}
return null; // Allowed
}
const server = Bun.serve({
async fetch(req) {
const url = new URL(req.url);
const ip = req.headers.get("x-forwarded-for") ?? "unknown";
if (url.pathname === "/api/login") {
const blocked = await checkLimit(authLimiter, ip);
if (blocked) return blocked;
return Response.json({ message: "Login endpoint" });
}
if (url.pathname.startsWith("/api/")) {
const blocked = await checkLimit(apiLimiter, ip);
if (blocked) return blocked;
return Response.json({ message: "API data" });
}
return Response.json({ message: "Welcome" });
},
port: 3000,
});
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 in your .env file
- Use
Bun.env.UNKEY_ROOT_KEY (not process.env)
- Check your root key has
ratelimit.*.limit permission
Bun loads .env automatically. Make sure the file is in your project root and restart the server.
Last modified on February 6, 2026