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:
Be an async function
The middleware must be an async fn.
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
}
Take Request
Must take exactly one Request as second-to-last argument.
Take Next
Must take Next as the last argument.
Return IntoResponse
Must return something that implements IntoResponse.
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
Modify request
Modify response
Read request body
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
}
Transform the response before sending to client: use axum :: {
extract :: Request ,
middleware :: Next ,
response :: Response ,
http :: header,
};
async fn add_security_headers (
request : Request ,
next : Next ,
) -> Response {
let mut response = next . run ( request ) . await ;
// Add security headers
let headers = response . headers_mut ();
headers . insert (
header :: HeaderName :: from_static ( "x-content-type-options" ),
"nosniff" . parse () . unwrap (),
);
headers . insert (
header :: HeaderName :: from_static ( "x-frame-options" ),
"DENY" . parse () . unwrap (),
);
response
}
Buffer and inspect the request body: use axum :: {
body :: { Body , Bytes },
extract :: Request ,
middleware :: Next ,
response :: { Response , IntoResponse },
http :: StatusCode ,
};
use http_body_util :: BodyExt ;
async fn print_request_body (
request : Request ,
next : Next ,
) -> Result < Response , StatusCode > {
let ( parts , body ) = request . into_parts ();
// Buffer the body
let bytes = body
. collect ()
. await
. map_err ( | _ | StatusCode :: INTERNAL_SERVER_ERROR ) ?
. to_bytes ();
// Log the body
if let Ok ( body_str ) = std :: str :: from_utf8 ( & bytes ) {
println! ( "Request body: {}" , body_str );
}
// Reconstruct the request with the buffered body
let request = Request :: from_parts ( parts , Body :: from ( bytes ));
Ok ( 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
}
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