Skip to main content
Axum is unique in that it doesn’t have its own bespoke middleware system and instead integrates with tower. This means the entire ecosystem of tower and tower-http middleware works seamlessly with axum.

Why tower?

While it’s not necessary to fully understand tower to write or use middleware with axum, having at least a basic understanding of tower’s concepts is recommended. Tower provides a standardized interface for composable middleware through the Service and Layer traits. Resources:

Applying middleware

Axum allows you to add middleware at multiple levels:
  • Entire routers: Router::layer and Router::route_layer
  • Method routers: MethodRouter::layer and MethodRouter::route_layer
  • Individual handlers: Handler::layer

Basic layer application

Applying multiple middleware

Use tower::ServiceBuilder to apply multiple middleware efficiently:

Commonly used tower-http middleware

The tower-http crate provides many production-ready middleware:

TraceLayer

High-level tracing and logging:

CorsLayer

Cross-Origin Resource Sharing (CORS) handling:

CompressionLayer

Automatic compression of responses:

TimeoutLayer

Timeout requests after a duration:

RequestIdLayer

Set and propagate request IDs:

Middleware ordering

Router::layer ordering

When you add middleware with Router::layer, all previously added routes are wrapped. Middleware executes from bottom to top:
Think of middleware as layers of an onion:
Execution flow:
  1. layer_three receives the request
  2. Passes to layer_two
  3. Passes to layer_one
  4. Passes to handler (produces response)
  5. Response goes back through layer_one
  6. Then layer_two
  7. Finally layer_three

ServiceBuilder ordering

ServiceBuilder composes layers to execute top to bottom, which is more intuitive:
With ServiceBuilder:
  1. layer_one receives request first
  2. Then layer_two
  3. Then layer_three
  4. Then handler
  5. Response bubbles back up through layer_three, layer_two, layer_one
Recommendation: Use ServiceBuilder for better readability and mental model.

The layer method

The layer method is available on Router, MethodRouter, and Handler. It accepts any type implementing tower::Layer:

Applying to routers

Applying to specific routes

Applying to handlers

Writing custom tower middleware

For maximum control, implement tower::Service and tower::Layer:
Use custom tower middleware when:
  • Middleware needs to be configurable via builder methods
  • You intend to publish middleware for others to use
  • You need maximum performance (avoid boxing futures)
Learn more: Building a middleware from scratch

Tower combinators

Tower provides utility combinators for simple transformations:

map_request

map_response

then

Chain an async function after the service:

and_then

Chain a fallible async function:

Wrapping the entire app

Apply middleware around your entire application by wrapping the Router (which implements Service):
This is useful for:
  • Middleware that needs to run before routing
  • URI rewriting middleware
  • Backpressure-sensitive middleware

Rewriting request URIs

Middleware added with Router::layer runs after routing. To rewrite URIs before routing, wrap the entire Router:

Error handling

Axum’s error handling model requires handlers to always return a response. Middleware can introduce errors, so they must be handled gracefully using HandleErrorLayer:
Important: HandleErrorLayer must be placed above middleware that can produce errors.

Accessing state in tower layers

Pass state to custom tower layers through the layer’s constructor:

Backpressure considerations

Axum expects services to not care about backpressure and always be ready. When routing to multiple services, axum:
  • Always returns Poll::Ready(Ok(())) from Service::poll_ready
  • Drives actual readiness inside the response future
Implications:
  • Avoid routing to backpressure-sensitive middleware
  • Use load shedding if needed
  • Apply backpressure-sensitive middleware around the entire app
  • Errors from poll_ready appear in the response future, not immediately
Note: Handlers created from async functions don’t care about backpressure and are always ready.

See also