Skip to content

Guards and Interceptors

Protect your routes and wrap handler execution with custom logic.

NestForge provides two primary mechanisms to intercept and control the request execution flow: Guards for access control and Interceptors for execution wrapping.


Guards have a single responsibility. They determine whether a given request will be handled by the route handler or not, based on certain conditions (like permissions, roles, ACLs, etc.) present at run-time.

In traditional web applications, authentication and authorization were often handled by middleware. While middleware is a fine choice for things like logging or header manipulation, it’s not ideal for authorization because it has no knowledge of which handler will be executed after it.

Guards have access to the RequestContext, metadata, and can be applied at the controller or route level.

The easiest way to create a guard is using the nestforge::guard! macro.

nestforge::guard!(ApiKeyGuard, |ctx| {
let has_key = ctx.headers.get("x-api-key").is_some();
if !has_key {
return Err(nestforge::HttpException::unauthorized("Missing API Key"));
}
Ok(())
});

You can apply guards at the controller level or route level using the #[nestforge::use_guards()] attribute.

#[controller("/admin")]
#[nestforge::use_guards(ApiKeyGuard)] // Applied to all routes in this controller
pub struct AdminController;
#[routes]
impl AdminController {
#[nestforge::get("/secret")]
#[nestforge::use_guards(SuperAdminGuard)] // Additional guard for this specific route
async fn get_secret() -> ApiResult<String> { ... }
}

Interceptors are inspired by the Aspect-Oriented Programming (AOP) technique. They allow you to wrap the handler execution, giving you the ability to:

  • Bind extra logic before and after method execution.
  • Transform the result returned from a function.
  • Transform the exception thrown from a function.
  • Completely override the handler execution (e.g., for caching purposes).

Using the nestforge::interceptor! macro:

nestforge::interceptor!(LoggingInterceptor, |ctx, req, next| {
let start = std::time::Instant::now();
let method = ctx.method.clone();
let path = ctx.uri.path().to_string();
let response = (next)(req).await;
println!("[{}] {} took {}ms", method, path, start.elapsed().as_millis());
response
});

Interceptors are perfect for transforming global response shapes.

nestforge::interceptor!(TransformInterceptor, |ctx, req, next| {
let response = (next)(req).await;
// Map the success response into a standard format
// ...
response
});

FeatureMiddlewareGuardsInterceptors
Aware of Handler?NoYesYes
Execution Order1st2nd3rd (Wraps Handler)
Best Use CaseRaw HTTP details (CORS, Logs)Authorization & PermissionsLogic wrapping, Caching, Mapping
Can block request?YesYesYes

You can register guards and interceptors globally in your main.rs:

NestForgeFactory::<AppModule>::create()?
.use_guard::<GlobalAuthGuard>()
.use_interceptor::<GlobalLoggingInterceptor>()
.listen(3000)
.await?;