Guards determine whether a request should proceed to the route handler. They implement the CanActivate interface and are automatically executed before the handler is called.
CanActivate Interface
interface CanActivate {
canActivate(context: ExecutionContext): Promise<boolean> | boolean;
}If canActivate returns false, the request is rejected with 403 Forbidden. If it returns true, the request proceeds normally.
Creating a Guard
import { Injectable, CanActivate, ExecutionContext } from "nestelia";
@Injectable()
class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>();
return request.headers.get("authorization") !== null;
}
}Guards can also be async:
@Injectable()
class RolesGuard implements CanActivate {
constructor(private readonly userService: UserService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>();
const token = request.headers.get("authorization");
const user = await this.userService.verifyToken(token);
return user?.role === "admin";
}
}@UseGuards() Decorator
Apply guards at the method level (single route) or class level (all routes in a controller). When both are present, class-level guards run first.
import { Controller, Get, UseGuards } from "nestelia";
@Controller("/admin")
@UseGuards(AuthGuard) // runs for every route in this controller
class AdminController {
@Get("/dashboard")
dashboard() {
return { data: "admin-only content" };
}
@Get("/stats")
@UseGuards(RolesGuard) // AuthGuard → RolesGuard → handler
stats() {
return { data: "stats" };
}
}Multiple guards can be chained — they run in order, and the first false stops the chain:
@UseGuards(AuthGuard, RolesGuard, IpWhitelistGuard)DI-Aware Guards
If a guard is registered as a provider in a module, it will be resolved from the DI container (allowing constructor injection). Otherwise it is instantiated directly.
@Module({
controllers: [AdminController],
providers: [AuthGuard, UserService], // AuthGuard gets DI
})
class AdminModule {}ExecutionContext
The ExecutionContext passed to canActivate provides access to the current request and handler metadata:
interface ExecutionContext {
/** Controller class */
getClass<T = any>(): T;
/** Route handler function */
getHandler(): (...args: unknown[]) => unknown;
/** All handler arguments */
getArgs<T extends any[] = any[]>(): T;
/** Single argument by index */
getArgByIndex<T = any>(index: number): T;
/** Context type — "http" for HTTP routes */
getType<T extends string = string>(): T;
/** Switch to HTTP context */
switchToHttp(): HttpArgumentsHost;
}
interface HttpArgumentsHost {
/** Web API Request object */
getRequest<T = any>(): T;
/** Elysia context (contains set.status, set.headers, etc.) */
getResponse<T = any>(): T;
}Accessing the raw request
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest<Request>();
const token = req.headers.get("authorization");
// ...
}Accessing the Elysia context (status, headers, cookies)
canActivate(context: ExecutionContext): boolean {
const ctx = context.switchToHttp().getResponse<ElysiaContext>();
const cookie = ctx.cookie["session"]?.value;
// ...
}Request Pipeline
Guards run after the controller and handler are resolved, before interceptors and the handler itself:
Request → Controller resolved → Guards → Interceptors → Handler → Response