Middleware and Tower
Express app.use vs Tower layers
Section titled “Express app.use vs Tower layers”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.
// Express — register middleware globallyimport 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);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]) ) );CORS layer
Section titled “CORS layer”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);Request tracing / logging
Section titled “Request tracing / logging”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.
Route-scoped vs global layers
Section titled “Route-scoped vs global layers”Tower layers wrap the service below them. You can attach layers to the whole router or to a specific route group:
// Express — scoped middlewareimport authMiddleware from './auth';
const protectedRouter = express.Router();protectedRouter.use(authMiddleware);protectedRouter.get('/profile', getProfile);
app.use('/api', protectedRouter);use axum::Router;use tower_http::auth::RequireAuthorizationLayer;
// Layer applied only to the protected sub-routerlet 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.