This version is in beta. Some features may change before release.

Middleware

A typed Middleware trait with before_request / after_response hooks, composed in a predictable onion order.

Middleware

axum and tower already give you Layer + Service, but writing one means learning poll-readiness, boxed futures, and the Service ownership rules. Most application middleware only wants two things: look at the request before the handler, and look at the response after. umbral's Middleware trait is exactly that: a before_request / after_response pair, typed for Rust.

Info
Reach for `Middleware` for the common "inspect/modify every request or response" case. When you genuinely need a tower `Layer` (timeouts, body-size limits, tracing spans), use a plugin's `wrap_router` to layer the raw axum router instead.

The trait

Code
rust
use umbral::prelude::*; // brings in Middleware
use axum::extract::Request;
use axum::response::{IntoResponse, Response};
use http::StatusCode;
 
struct RequireApiKey;
 
#[umbral::async_trait]
impl Middleware for RequireApiKey {
async fn before_request(&self, req: Request) -> Result<Request, Response> {
if req.headers().get("x-api-key").is_some() {
Ok(req) // continue to the handler
} else {
Err((StatusCode::UNAUTHORIZED, "missing API key").into_response()) // short-circuit
}
}
 
async fn after_response(&self, mut res: Response) -> Response {
res.headers_mut().insert("x-powered-by", "umbral".parse().unwrap());
res
}
}

Both hooks have a default pass-through, so implement only the one you need. #[umbral::async_trait] is re-exported from the facade, so no direct async-trait dependency is required.

Registering it

Two ways, and they share one ordered stack:

App-level middleware is added to the stack first, then each plugin's contribution in topological dependency order.

Controlling order

That insertion order is just the default. Override Middleware::order(&self) -> i32 to place a middleware declaratively - lower values are outer (its before_request runs earlier and its after_response runs later). The stack is stable-sorted by order before it's installed, so a middleware lands in the right place regardless of which plugin registered it or when:

Code
rust
impl Middleware for SessionLoader {
fn order(&self) -> i32 { -100 } // outermost: runs before auth, unwinds last
// before_request / after_response …
}
 
impl Middleware for AuthGate {
fn order(&self) -> i32 { -50 } // inside the session, outside the app
// …
}

Middleware with equal order keep their registration order (app-level before plugin-level; plugins in dependency order). The default is 0.

Composition: the onion

before_request hooks run in registration order; after_response hooks run in the reverse order, so each middleware wraps the ones registered after it. With a stack [A, B, C] and a request that reaches the handler:

Code
txt
A.before → B.before → C.before → handler → C.after → B.after → A.after

This onion model is what makes composition predictable: an outer middleware always sees the request first and the response last.

Short-circuiting

A before_request that returns Err(response) stops the chain: the handler and every later middleware are skipped. Only the middleware whose before_request already ran get an after_response, still in reverse. If B short-circuits in the stack above:

Code
txt
A.before → B.before (Err) → A.after → (response returned)

C and the handler never run; A, which already processed the request, still gets to process the rejection response.

Where it sits in the layer stack

The middleware stack is installed so that umbral's own security and transport layers stay outermost: host-header validation, CORS, and compression all run before your middleware ever sees the request, and the 404 fallback is inside it (so your middleware observes misses too). You don't configure this; it's wired at App::build.

See also

  • Routes - declaring the handlers your middleware wraps.
  • Auth gating - the built-in login-required guard.
  • crates/umbral-core/src/middleware.rs for the trait + stack implementation.
webmiddlewareplugins