Skip to content

Middleware and Tower

In Express, middleware is a function (req, res, next) => void that you register with app.use(). In Axum, middleware is a Tower layer — a composable wrapper around a Service. The concept is the same (intercept → process → forward), but Tower is protocol-agnostic and works at compile time.

TypeScript
// Express — register middleware globally
import express from 'express';
import cors from 'cors';
import morgan from 'morgan';
const app = express();
app.use(cors({ origin: 'https://myapp.com' }));
app.use(morgan('combined'));
app.use(express.json());
app.get('/users', listUsers);
Rust
use axum::{routing::get, Router};
use tower_http::{cors::CorsLayer, trace::TraceLayer};
use tower::ServiceBuilder;
use http::HeaderValue;
let app = Router::new()
.route("/users", get(list_users))
.layer(
ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(
CorsLayer::new()
.allow_origin("https://myapp.com".parse::<HeaderValue>().unwrap())
.allow_methods([http::Method::GET, http::Method::POST])
)
);

tower-http ships a CorsLayer that mirrors the cors npm package. Add it to Cargo.toml with tower-http = { version = "0.5", features = ["cors"] }.

use tower_http::cors::{Any, CorsLayer};
use axum::http::Method;
let cors = CorsLayer::new()
.allow_origin(Any) // any origin (dev only)
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
.allow_headers(Any);
let app = Router::new()
.route("/users", get(list_users))
.layer(cors);

tower-http’s TraceLayer integrates with tracing to log every request/response. Add tower-http = { version = "0.5", features = ["trace"] } to Cargo.toml.

use tower_http::trace::TraceLayer;
let app = Router::new()
.route("/users", get(list_users))
.layer(TraceLayer::new_for_http());

Start the app with RUST_LOG=tower_http=debug cargo run to see structured request logs.

Tower layers wrap the service below them. You can attach layers to the whole router or to a specific route group:

TypeScript
// Express — scoped middleware
import authMiddleware from './auth';
const protectedRouter = express.Router();
protectedRouter.use(authMiddleware);
protectedRouter.get('/profile', getProfile);
app.use('/api', protectedRouter);
Rust
use axum::Router;
use tower_http::auth::RequireAuthorizationLayer;
// Layer applied only to the protected sub-router
let protected_routes = Router::new()
.route("/profile", get(get_profile))
.layer(RequireAuthorizationLayer::bearer("secret-token"));
let app = Router::new()
.route("/health", get(health))
.nest("/api", protected_routes);

Run locally — middleware requires a running Axum server. Run with RUST_LOG=tower_http=debug cargo run.

What is the Axum/Tower equivalent of `app.use(cors())`?
Which crate provides `CorsLayer` and `TraceLayer`?
How do you apply a Tower layer to only a specific group of routes?