← Week 3: Axum Web Framework

Day 17: Axum Middleware with Tower

Phase 2 · Jun 26, 2026

← Week 3: Axum Web Framework

Agenda (2–3 hours)

  • Read (45 min): Axum middleware docs; tower_http crate README; axum::middleware::from_fn
  • Study (45 min): What can from_fn middleware do vs what requires a full Tower Layer? When is each appropriate?
  • Practice (45 min): Add request logging, CORS, compression, and auth middleware to an Axum router
  • Challenge (30 min): Implement an authentication middleware that reads a JWT from Authorization: Bearer, validates it, and injects the claims into request extensions
← Week 3: Axum Web Framework

Three Ways to Add Middleware

// 1. Route-specific Tower layer
Router::new()
    .route("/admin", get(admin_handler))
    .route_layer(middleware::from_fn(auth_middleware));

// 2. Router-wide Tower layer
Router::new()
    .route("/health", get(health))
    .layer(TraceLayer::new_for_http());

// 3. ServiceBuilder wrapping the whole app
let app = ServiceBuilder::new()
    .layer(CompressionLayer::new())
    .layer(TimeoutLayer::new(Duration::from_secs(10)))
    .service(router);
← Week 3: Axum Web Framework

tower_http Batteries

use tower_http::{
    trace::TraceLayer,
    compression::CompressionLayer,
    cors::CorsLayer,
    timeout::TimeoutLayer,
    limit::RequestBodyLimitLayer,
    auth::RequireAuthorizationLayer,
};

let app = Router::new()
    .route("/", get(handler))
    .layer(
        ServiceBuilder::new()
            .layer(TraceLayer::new_for_http())
            .layer(CompressionLayer::new())
            .layer(CorsLayer::permissive())
            .layer(RequestBodyLimitLayer::new(4 * 1024 * 1024))
    );
← Week 3: Axum Web Framework

from_fn Middleware

async fn auth_middleware(
    State(state): State<AppState>,
    mut req: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let token = req
        .headers()
        .get(AUTHORIZATION)
        .and_then(|v| v.to_str().ok())
        .and_then(|s| s.strip_prefix("Bearer "))
        .ok_or(StatusCode::UNAUTHORIZED)?;

    let claims = state.jwt.verify(token)
        .map_err(|_| StatusCode::UNAUTHORIZED)?;

    // Inject into extensions for downstream handlers
    req.extensions_mut().insert(claims);
    Ok(next.run(req).await)
}
← Week 3: Axum Web Framework

Layer Ordering in Axum

Layers added later wrap layers added earlier — outermost layer is applied last.

Router::new()
    .route("/", get(handler))
    .layer(auth_layer)       // inner (closer to handler)
    .layer(logging_layer)    // outer (runs before auth_layer)

Execution order: logging → auth → handler.

For ServiceBuilder, the order is reversed: first .layer() call is the outermost.

← Week 3: Axum Web Framework

Key Takeaways

  • from_fn is the quick path for middleware that doesn't need to be a full Layer
  • tower_http provides production-ready middleware: tracing, CORS, compression, timeouts
  • Use route_layer for middleware that only applies to specific routes
  • Layer ordering matters — understand inside-out vs outside-in when composing

Tomorrow: error handling — returning rich error responses from handlers.