Middleware in Rust: Axum, Actix & Tower Service Guide

Max WellsMax WellsFounder of Rustify

TL;DR: Middleware is code that runs between receiving an HTTP request and sending a response. In Rust, middleware is implemented via the Tower Service trait (used by Axum) or the Transform trait (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
└────────┬────────┘


      Response

How 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 responses

Writing 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.

MiddlewareCrateFramework
Request tracing/loggingtower-http::traceAxum
CORS headerstower-http::corsAxum
Gzip compressiontower-http::compressionAxum
Request timeouttower-http::timeoutAxum
Rate limitingtower_governorAxum
Session authaxum-loginAxum
Loggeractix-web::middleware::LoggerActix Web
JWT validationactix-web-httpauthActix 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: The Service trait 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

Ready to Land a $120k+ Rust Job in the US or Europe?