TL;DR: Middleware is code that runs between receiving an HTTP request and sending a response. In Rust, middleware is implemented via the Tower
Servicetrait (used by Axum) or theTransformtrait (used by Actix Web). Common uses: authentication, logging, rate limiting, CORS, and request tracing. Middleware wraps handlers in layers; each layer can inspect, modify, or short-circuit the request before passing it on.
What Is Middleware?
Middleware is a function or type that intercepts an HTTP request before it reaches your handler and/or intercepts the response before it reaches the client; allowing cross-cutting concerns to be handled once, centrally, rather than repeated in every handler.
The concept exists in every web framework across all languages. In Rust, the exact mechanism differs by framework, but the mental model is the same: a stack of layers, each wrapping the next.
Request
│
▼
┌─────────────────┐
│ Logging │ ← logs request method + path
└────────┬────────┘
│
▼
┌─────────────────┐
│ Auth check │ ← returns 401 if token invalid
└────────┬────────┘
│
▼
┌─────────────────┐
│ Your handler │ ← runs only if all middleware passed
└────────┬────────┘
│
▼
ResponseHow Does Middleware Work in Axum?
In Axum, middleware is implemented via the Tower Service trait and applied using .layer() on a Router. Each layer wraps the inner service, forming a chain.
Adding middleware from tower-http:
use axum::{Router, routing::get};
use tower_http::{
trace::TraceLayer,
cors::CorsLayer,
compression::CompressionLayer,
};
let app = Router::new()
.route("/api/users", get(list_users))
.layer(TraceLayer::new_for_http()) // logs every request
.layer(CorsLayer::permissive()) // adds CORS headers
.layer(CompressionLayer::new()); // gzip responsesWriting a custom middleware function in Axum:
use axum::{middleware::Next, extract::Request, response::Response};
async fn require_auth(
request: Request,
next: Next,
) -> Result<Response, StatusCode> {
let token = request
.headers()
.get("Authorization")
.and_then(|v| v.to_str().ok());
match token {
Some(t) if is_valid(t) => Ok(next.run(request).await),
_ => Err(StatusCode::UNAUTHORIZED),
}
}
// Apply it to specific routes only:
let protected = Router::new()
.route("/dashboard", get(dashboard))
.layer(axum::middleware::from_fn(require_auth));How Does Middleware Work in Actix Web?
In Actix Web, middleware implements the Transform and Service traits and is registered with .wrap() on an App or Scope.
use actix_web::{web, App, HttpServer, middleware};
HttpServer::new(|| {
App::new()
.wrap(middleware::Logger::default()) // request logging
.wrap(middleware::Compress::default()) // response compression
.wrap(middleware::NormalizePath::trim()) // strip trailing slashes
.route("/api/users", web::get().to(list_users))
})What Are the Most Common Middleware in Rust?
The most widely used middleware covers logging, authentication, CORS, rate limiting, and compression; all available as battle-tested crates.
| Middleware | Crate | Framework |
|---|---|---|
| Request tracing/logging | tower-http::trace | Axum |
| CORS headers | tower-http::cors | Axum |
| Gzip compression | tower-http::compression | Axum |
| Request timeout | tower-http::timeout | Axum |
| Rate limiting | tower_governor | Axum |
| Session auth | axum-login | Axum |
| Logger | actix-web::middleware::Logger | Actix Web |
| JWT validation | actix-web-httpauth | Actix Web |
What Is the Tower Service Trait?
Tower's Service trait is the common abstraction for middleware in the Rust async ecosystem; a type that takes a request and asynchronously returns a response, optionally delegating to an inner service.
pub trait Service<Request> {
type Response;
type Error;
type Future: Future<Output = Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
fn call(&mut self, req: Request) -> Self::Future;
}Every Axum router, every middleware, and every handler is ultimately a Service. This uniformity means any Tower middleware works with Axum, hyper, tonic, and any other Tower-compatible framework without modification.
Frequently Asked Questions
Extractors run per-handler and pull specific data out of the request (JSON body, path params, headers). Middleware runs for every request on the router it is applied to and can short-circuit before the handler is ever called. Use extractors for handler-specific logic; use middleware for cross-cutting concerns like auth and logging.
Yes. In Axum, apply .layer() to a nested Router instead of the top-level one. Only routes inside that router will pass through the middleware. In Actix Web, use web::scope() with .wrap() to limit middleware to a path prefix.
In Axum, use Extension to insert values into the request extensions from middleware, then extract them in handlers with Extension<T>. Alternatively, use State<T> for data initialized at startup. Middleware can mutate request extensions before passing the request to the handler.
Each middleware layer adds a small overhead; typically a function call and a future allocation. In practice, the overhead is negligible compared to actual I/O. Logging middleware that writes to disk is the most common source of middleware-induced latency.
Sources
- Tower Documentation: The
Servicetrait and middleware primitives - tower-http on docs.rs: HTTP-specific middleware for Axum
- Axum Middleware Guide: Official Axum middleware docs
Related Glossary Terms
- Tower: The
Servicetrait that powers Axum middleware - Axum: Rust web framework built on Tower middleware
- Actix Web: Alternative framework with its own middleware system
- Tokio: The async runtime middleware executes on
Keep Reading
- Rust vs Go for Backend Development: middleware patterns compared across ecosystems
- Rust at Cloudflare: middleware at the network edge
- Rust on AWS Lambda: request middleware in serverless Rust handlers
