Axum: Get Tracing, Compression, and Timeouts Without Building a Middleware System
You've probably written the same middleware plumbing more than once—logging here, a timeout layer there, compression bolted on somewhere else. What if your HTTP framework just handed you all of that because it borrowed an existing ecosystem instead of reinventing one? That's the pitch behind axum, an HTTP routing and request-handling library from the tokio-rs team that focuses on ergonomics and modularity.
What It Does
At its core, axum routes requests to handlers and lets you parse incoming requests declaratively using extractors. It's built on top of hyper and integrates directly with the tower and tower-http ecosystems. Rather than shipping its own middleware system, axum uses tower::Service as its abstraction layer.
The high-level feature list is short and to the point: route requests to handlers with a macro-free API, parse requests with extractors, handle errors in a simple and predictable way, and generate responses with minimal boilerplate. The last feature is the one that shapes everything else—taking full advantage of tower and tower-http for middleware, services, and utilities.
It's written in Rust, uses #![forbid(unsafe_code)] to guarantee everything is implemented in 100 percent safe Rust, and has a minimum supported Rust version of 1.80. Performance-wise, it's a thin layer over hyper, so it adds very little overhead.
Why It's Cool
It doesn't have a middleware system, and that's the point. Most frameworks build their own middleware abstractions, which means the middleware you write only works inside that framework. axum skips this entirely by leaning on tower::Service. The result is that you get timeouts, tracing, compression, authorization, and more for free—not because axum implemented them, but because tower already did.
Your middleware isn't trapped. Because axum uses the same Service trait that tower defines, you can share middleware with applications written using hyper or tonic. If you've got a gRPC service and an HTTP service in the same codebase, they can reuse the same layers. That's a real practical win, not a theoretical one.
The API stays out of your way. The routing API is macro-free, which means no proc-macro magic obscuring what's happening. You write Router::new().route("/", get(root)) and that's it. Handlers are just async functions, and extractors like Json(payload) tell axum how to parse the request body—the function signature does the work.
Extractors make request parsing declarative. Instead of manually pulling apart a request, you describe what you want in the handler's arguments. In the README example, Json(payload): Json<CreateUser> parses the body into a typed struct. The handler returns (StatusCode, Json<User>), which becomes a JSON response with a status code. Minimal boilerplate, clear intent.
Error handling is predictable. The README calls out a "simple and predictable error handling model" as a core feature. That matters—error handling is usually where frameworks get messy, and having it be a first-class design concern is worth noting.
It's honest about performance. Rather than claiming to be the fastest thing ever, the README says performance is "comparable to hyper" and links to third-party benchmarks. That kind of restraint is refreshing.
How to Try It
Getting started is straightforward if you're already in the Rust ecosystem.
-
Add
axumto yourCargo.toml(check crates.io for the current version—the README notes thatmaincurrently contains breaking changes toward 0.9, so use the0.8.xbranch for what's released). -
Build a router and serve it. The README example is a complete, runnable app:
use axum::{
routing::{get, post},
http::StatusCode,
Json, Router,
};
use serde::{Deserialize, Serialize};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let app = Router::new()
.route("/", get(root))
.route("/users", post(create_user));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await;
}
- Check the examples directory in the repo for more complete projects, and the crate documentation for additional snippets.
The repository is at github.com/tokio-rs/axum. If you get stuck, there's a Discord channel and a discussions forum linked from the README.
Final Thoughts
axum is best suited for developers who are already comfortable with tower and hyper, or who are willing to learn them—because that's where a lot of its power lives. If you want a framework that hides all the plumbing, this might not be the one. But if you want routing that composes cleanly with middleware you can reuse elsewhere, the design is hard to argue with. The fact that it's from the tokio-rs team and uses 100 percent safe Rust doesn't hurt either. Worth a look if you're building HTTP services in Rust.
Follow @githubprojects for more developer tools and open source projects.