---
title: How to use tower middleware with Axum
summary: Stack Tower middleware in Axum using Router::layer or ServiceBuilder to add reusable request handling logic.
lang: en
slug: how-to-use-tower-middleware-with-axum
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-use-tower-middleware-with-axum/
---

# How to use tower middleware with Axum



## When handlers get too heavy

You are building an API. Every endpoint needs request logging. Every endpoint needs CORS headers. Every endpoint needs rate limiting or authentication. Copying that logic into each handler turns your code into a maintenance nightmare. You want a single place to attach cross-cutting concerns, and you want it to work without touching your route handlers.

## The checkpoint model

Middleware is a series of transparent filters that wrap your application. Think of it like a series of security checkpoints at an airport. Your request is the passenger. It passes through baggage check, then ticket verification, then metal detection. Each checkpoint can inspect the passenger, modify their documents, or turn them away. When the passenger finally reaches the gate, your route handler, they have already been processed by every checkpoint. On the way back out, the response passes through the same checkpoints in reverse order, allowing each layer to modify the headers or body before it leaves the building.

Tower provides the checkpoint infrastructure. Axum provides the gate. You just supply the security guards. The entire system relies on two traits: `Service` and `Layer`. A `Service` is anything that takes a request and returns a response. A `Layer` is a factory that takes a `Service` and wraps it in a new `Service`. When you attach middleware, you are asking a `Layer` to wrap your router in a new `Service`. The compiler verifies that the output type of one layer matches the input type of the next. Type safety guarantees your middleware chain will not panic at runtime.

## A single layer in action

Start with one layer to see how Axum accepts it. The `Router` struct implements `Service`, which means it can be wrapped directly.

```rust
use axum::{routing::get, Router};
use tower_http::trace::TraceLayer;

/// Serves a basic HTTP server with request tracing enabled.
#[tokio::main]
async fn main() {
    // Wrap the router with a tracing layer to log incoming requests.
    let app = Router::new()
        .route("/", get(|| async { "Hello" }))
        .layer(TraceLayer::new_for_http());

    // Bind to localhost and start listening for connections.
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
```

The `Router::layer` method takes any type that implements `tower::Layer`. `TraceLayer` implements that trait. When you call `.layer()`, Axum hands its internal service to the layer. The layer returns a new service that delegates to the original router but adds logging before and after each request. You do not need to change your route handlers. The layer sits outside them.

Keep middleware at the router level unless you have a specific reason to isolate it. Wrapping the entire router is the standard pattern.

## How the wrapping actually happens

Under the hood, Tower uses a functional composition model. Each call to `.layer()` creates a new `Service` that holds a reference to the previous one. The request flows inward through the layers, reaches your handler, and the response flows outward.

When a request arrives, the outermost layer receives it first. It can read headers, mutate the request body, or reject the request early. If it allows the request through, it calls `.call()` on the inner service. This repeats until the request reaches your Axum handler. Your handler returns a response. The response travels back outward through the same layers in reverse order. Each layer can modify the response headers, compress the body, or measure latency.

The compiler enforces this chain at compile time. If a layer changes the request type from `Request<Body>` to `Request<ModifiedBody>`, the next layer must accept `Request<ModifiedBody>`. This is why middleware order is strict. The compiler will reject mismatched types with E0277 (trait bound not satisfied) or E0308 (mismatched types) if you chain layers that expect different request or response shapes.

Treat the middleware chain as a type-safe pipeline. If the types do not line up, the compiler is protecting you from a runtime panic.

## Stacking layers in production

Real applications need more than one layer. CORS, compression, tracing, and rate limiting all stack together. Chaining `.layer()` calls works, but the syntax gets noisy. The ecosystem standard is `tower::ServiceBuilder`.

```rust
use axum::{routing::get, Router};
use tower::{ServiceBuilder, ServiceExt};
use tower_http::{
    cors::CorsLayer,
    trace::TraceLayer,
    compression::CompressionLayer,
};

/// Configures and runs a production-ready HTTP server.
#[tokio::main]
async fn main() {
    // Build a reusable layer stack that applies to the entire router.
    let middleware_stack = ServiceBuilder::new()
        .layer(CompressionLayer::new())
        .layer(CorsLayer::permissive())
        .layer(TraceLayer::new_for_http());

    // Apply the complete stack to the router in a single call.
    let app = Router::new()
        .route("/", get(|| async { "Hello" }))
        .layer(middleware_stack);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
```

`ServiceBuilder` collects layers and returns a single `Layer` that applies them all. The convention in the Rust ecosystem is to write `ServiceBuilder::new()` and chain `.layer()` calls in the order you want them applied. The last layer you add becomes the outermost wrapper. This means the last layer in the chain runs first on the request, and first on the response.

A quick convention note: always use `tower_http` for HTTP-specific middleware like CORS, compression, and tracing. The base `tower` crate only provides generic utilities like timeouts, retries, and load balancing. Mixing them is fine, but keep HTTP concerns in `tower_http` to match community expectations.

Write your `ServiceBuilder` chain bottom-up. The last `.layer()` call is the first checkpoint your request hits.

## Where things break

Middleware chains fail in predictable ways. The most common issue is order. If you put CORS after your authentication layer, the preflight `OPTIONS` request will never reach the CORS handler. The browser will block the request before your app even sees it. Put CORS at the outermost position.

Type mismatches are the second trap. Some middleware changes the request body type. `tower_http::compression::CompressionLayer` expects a specific body type. If you wrap it around a layer that already modified the body, the compiler rejects it with E0277. The fix is to place body-modifying layers at the innermost position, closest to your handlers.

State sharing causes the third common error. Middleware cannot use Axum's `State` extractor directly. Middleware operates at the `Service` level, which predates Axum's routing. If you need shared configuration in middleware, pass it through the layer constructor, not through route extractors. Trying to force `State` into a `Layer` will trigger E0599 (no function named `call` found) because the trait bounds will not align.

Keep your layers thin and focused. If a layer needs to mutate state, wrap it in a `tokio::sync::Mutex` or use `Arc` to share read-only configuration. Do not fight the service boundary.

## Picking the right tool

Use `Router::layer` when you have a single piece of middleware and want to attach it directly to the router. Use `ServiceBuilder` when you need to stack multiple layers and want a clean, reusable configuration block. Use `Router::route_layer` when you want middleware to apply only to specific routes instead of the entire application. Use a custom `tower::Service` when you need fine-grained control over the request/response lifecycle and existing layers do not fit your use case. Reach for `tower_http` when you need standard HTTP concerns like CORS, compression, or tracing. Reach for base `tower` when you need generic utilities like timeouts, retries, or load balancing.

Match the tool to the scope. Global concerns go on the router. Route-specific concerns go on the route. Custom logic gets its own service.

## Where to go next

- [How to use WebSockets in Rust](/en/how-to-use-websockets-in-rust/)
- [How to handle form data](/en/how-to-handle-form-data/)
- [How to Build Serverless Applications in Rust](/en/how-to-build-serverless-applications-in-rust/)