Skip to main content
Middleware allows you to process requests before they reach handlers and modify responses before they’re sent to clients. Axum supports both custom middleware and Tower middleware.

Middleware with from_fn

The simplest way to create middleware is using middleware::from_fn:
use axum::{
    Router,
    routing::get,
    middleware::{self, Next},
    extract::Request,
    response::Response,
};

async fn my_middleware(
    request: Request,
    next: Next,
) -> Response {
    // Do something with the request...
    println!("Request: {} {}", request.method(), request.uri());
    
    // Call the next middleware or handler
    let response = next.run(request).await;
    
    // Do something with the response...
    println!("Response status: {}", response.status());
    
    response
}

let app = Router::new()
    .route("/", get(|| async { "Hello, World!" }))
    .layer(middleware::from_fn(my_middleware));

Middleware signature

Middleware functions created with from_fn must:
1

Be an async function

The middleware must be an async fn.
2

Take extractors (optional)

Can take zero or more FromRequestParts extractors before the request.
use axum::{
    extract::Request,
    http::HeaderMap,
    middleware::Next,
    response::Response,
};

async fn middleware(
    headers: HeaderMap,  // Extractor
    request: Request,
    next: Next,
) -> Response {
    next.run(request).await
}
3

Take Request

Must take exactly one Request as second-to-last argument.
4

Take Next

Must take Next as the last argument.
5

Return IntoResponse

Must return something that implements IntoResponse.

Using extractors in middleware

Extract data from requests in middleware:
use axum::{
    Router,
    routing::get,
    middleware::{self, Next},
    extract::Request,
    response::Response,
    http::{HeaderMap, StatusCode},
};

async fn auth_middleware(
    headers: HeaderMap,
    request: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    // Check authorization header
    match headers.get("authorization") {
        Some(token) if is_valid_token(token) => {
            Ok(next.run(request).await)
        }
        _ => Err(StatusCode::UNAUTHORIZED),
    }
}

fn is_valid_token(_token: &axum::http::HeaderValue) -> bool {
    true // Simplified validation
}

let app = Router::new()
    .route("/protected", get(|| async { "Secret data" }))
    .layer(middleware::from_fn(auth_middleware));

Middleware with state

Access application state in middleware using from_fn_with_state:
use axum::{
    Router,
    routing::get,
    extract::{State, Request},
    middleware::{self, Next},
    response::Response,
    http::StatusCode,
};

#[derive(Clone)]
struct AppState {
    api_key: String,
}

async fn require_api_key(
    State(state): State<AppState>,
    request: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let provided_key = request
        .headers()
        .get("x-api-key")
        .and_then(|v| v.to_str().ok());
    
    if provided_key == Some(&state.api_key) {
        Ok(next.run(request).await)
    } else {
        Err(StatusCode::UNAUTHORIZED)
    }
}

let state = AppState {
    api_key: "secret-key".to_string(),
};

let app = Router::new()
    .route("/", get(|| async { "Protected" }))
    .layer(middleware::from_fn_with_state(state.clone(), require_api_key))
    .with_state(state);

Modifying requests and responses

Transform the request before it reaches handlers:
use axum::{
    extract::Request,
    middleware::Next,
    response::Response,
    http::header,
};

async fn add_request_id(
    mut request: Request,
    next: Next,
) -> Response {
    // Add a custom header to the request
    let request_id = uuid::Uuid::new_v4().to_string();
    request.headers_mut().insert(
        header::HeaderName::from_static("x-request-id"),
        request_id.parse().unwrap(),
    );
    
    next.run(request).await
}

Layer ordering

Middleware layers are applied in reverse order. The last layer added is executed first.
use axum::{
    Router,
    routing::get,
    middleware::{self, Next},
    extract::Request,
    response::Response,
};

async fn first_middleware(request: Request, next: Next) -> Response {
    println!("First: before");
    let response = next.run(request).await;
    println!("First: after");
    response
}

async fn second_middleware(request: Request, next: Next) -> Response {
    println!("Second: before");
    let response = next.run(request).await;
    println!("Second: after");
    response
}

let app = Router::new()
    .route("/", get(|| async { "Handler" }))
    .layer(middleware::from_fn(first_middleware))
    .layer(middleware::from_fn(second_middleware));

// Execution order:
// 1. Second: before
// 2. First: before  
// 3. Handler
// 4. First: after
// 5. Second: after

Tower middleware

Use any Tower middleware with Axum:
use axum::Router;
use tower::ServiceBuilder;
use tower_http::{
    trace::TraceLayer,
    compression::CompressionLayer,
    timeout::TimeoutLayer,
};
use std::time::Duration;

let app = Router::new()
    .layer(
        ServiceBuilder::new()
            .layer(TraceLayer::new_for_http())
            .layer(CompressionLayer::new())
            .layer(TimeoutLayer::new(Duration::from_secs(10)))
    );

Route-specific middleware

Apply middleware to specific routes:
use axum::{
    Router,
    routing::get,
    middleware,
};

async fn admin_only(
    request: axum::extract::Request,
    next: axum::middleware::Next,
) -> axum::response::Response {
    // Check admin permissions...
    next.run(request).await
}

let app = Router::new()
    .route("/public", get(|| async { "Public" }))
    .route(
        "/admin",
        get(|| async { "Admin" })
            .layer(middleware::from_fn(admin_only))
    );
Alternatively, use route_layer:
use axum::{
    Router,
    routing::get,
    middleware,
};

let app = Router::new()
    .route("/admin/users", get(|| async { "Users" }))
    .route("/admin/settings", get(|| async { "Settings" }))
    .route_layer(middleware::from_fn(admin_only))
    .route("/public", get(|| async { "Public" }));

async fn admin_only(
    request: axum::extract::Request,
    next: axum::middleware::Next,
) -> axum::response::Response {
    next.run(request).await
}

// admin_only only applies to routes added before route_layer

Common middleware patterns

use axum::{
    extract::Request,
    middleware::Next,
    response::Response,
};

async fn logging_middleware(
    request: Request,
    next: Next,
) -> Response {
    let method = request.method().clone();
    let uri = request.uri().clone();
    
    let start = std::time::Instant::now();
    let response = next.run(request).await;
    let duration = start.elapsed();
    
    println!(
        "{} {} - {} ({:?})",
        method,
        uri,
        response.status(),
        duration
    );
    
    response
}
use axum::{
    extract::Request,
    middleware::Next,
    response::Response,
    http::header,
};

async fn cors_middleware(
    request: Request,
    next: Next,
) -> Response {
    let mut response = next.run(request).await;
    
    let headers = response.headers_mut();
    headers.insert(
        header::ACCESS_CONTROL_ALLOW_ORIGIN,
        "*".parse().unwrap(),
    );
    headers.insert(
        header::ACCESS_CONTROL_ALLOW_METHODS,
        "GET, POST, PUT, DELETE".parse().unwrap(),
    );
    
    response
}
For production, use the tower-http CORS layer instead.

Error handling in middleware

Handle middleware errors appropriately:
use axum::{
    extract::Request,
    middleware::Next,
    response::{Response, IntoResponse},
    http::StatusCode,
};

async fn fallible_middleware(
    request: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    // Validate something that might fail
    if !is_valid(&request) {
        return Err(StatusCode::BAD_REQUEST);
    }
    
    Ok(next.run(request).await)
}

fn is_valid(_request: &Request) -> bool {
    true
}

Next steps

Error Handling

Learn how to handle errors from middleware

State

Use state in your middleware