How to Scope Elysia Rate Limit to a Single Route in ElysiaJS
Understanding Route-Level Rate Limiting in ElysiaJS
When building web applications with ElysiaJS and Bun, applying rate limiting to sensitive endpoints—such as user registration or password resets—is crucial to prevent brute-force attacks and spam. However, plugins attached via the .use() method in Elysia apply to all subsequent routes within that instance. If you want to restrict elysia-rate-limit strictly to a single route (e.g., POST /register) without affecting adjacent routes like POST /login, you need to isolate the plugin's scope.
The Problem: Global and Instance-Wide Plugin Application
In Elysia, chaining .use(rateLimit(...)) at the top of a route file applies the rate limiter middleware to every handler defined after it in that instance. If your authRoutes instance defines both /register and /login, attaching rate limiting at the instance level will inadvertently throttle requests to /login as well.
Solution 1: Encapsulate the Route in a Sub-Instance (Recommended)
The cleanest and most idiomatic way to isolate plugins in ElysiaJS is to create an inline sub-instance for the route requiring rate limiting. This keeps your code modular and ensures the middleware only runs where intended.
// auth.routes.ts
import { Elysia } from "elysia";
import { registerRateLimit } from "../plugins/rate-limit";
export const authRoutes = new Elysia()
// Isolated sub-instance with rate limiting applied only to /register
.use(
new Elysia()
.use(registerRateLimit)
.post("/register", registerHandler)
)
// Unaffected route outside the sub-instance scope
.post("/login", loginHandler);In this architecture, registerRateLimit is scoped exclusively to the child Elysia instance containing /register, leaving /login completely unrestricted by that specific rate limit.
Solution 2: Using the Conditional skip Option
If you prefer to keep all routes flat in a single instance, elysia-rate-limit provides a built-in skip option. You can pass a function to evaluate the incoming request URL and skip rate limiting for any endpoint other than /register.
// rate-limit.ts
import { rateLimit } from "elysia-rate-limit";
export const registerRateLimit = rateLimit({
duration: 900000, // 15 minutes
max: 1,
errorResponse: "Too many registration attempts, try again later.",
skip: (request) => {
const url = new URL(request.url);
// Skip rate limiting if the path is NOT /register
return url.pathname !== "/register";
}
});Then simply register the plugin in your main route instance:
// auth.routes.ts
import { Elysia } from "elysia";
import { registerRateLimit } from "../plugins/rate-limit";
export const authRoutes = new Elysia()
.use(registerRateLimit)
.post("/register", registerHandler)
.post("/login", loginHandler);Which Approach Should You Choose?
- Sub-Instance Approach: Highly recommended. It leverages Elysia's native context scoping, leading to cleaner code without runtime string checks on every request URL.
- Conditional
skipApproach: Helpful if you want centralized rate limiting rules with path-based filtering or dynamic bypass logic.