What it is
Axum is a web framework from the Tokio team, built on Tower and Hyper, with routing and handler argument extraction driven entirely by the type system.
Handlers are async functions whose parameters are extractors: Path, Query, Json, State, headers. The return type decides the response.
Installation
cargo add axum tokio --features tokio/fullGetting started
The smallest useful thing you can do with it, and what each part means.
use axum::{Router, routing::get, extract::{Path, State, Json}, http::StatusCode};
async fn get_book(
State(db): State<Pool>,
Path(id): Path<i64>,
) -> Result<Json<Book>, StatusCode> {
let book = db.find(id).await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
book.map(Json).ok_or(StatusCode::NOT_FOUND)
}
let app = Router::new()
.route("/books/{id}", get(get_book))
.with_state(pool);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app).await?;enum AppError { NotFound, Database(sqlx::Error) }
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match self {
AppError::NotFound => (StatusCode::NOT_FOUND, "not found"),
AppError::Database(e) => {
tracing::error!(?e, "database failure"); // log the detail
(StatusCode::INTERNAL_SERVER_ERROR, "internal error")
}
};
(status, Json(json!({ "error": message }))).into_response()
}
}Advanced usage
Where the library earns its place over a simpler alternative.
use tower_http::{trace::TraceLayer, cors::CorsLayer, timeout::TimeoutLayer};
let app = Router::new()
.route("/books", get(list_books))
.layer(TraceLayer::new_for_http())
.layer(TimeoutLayer::new(Duration::from_secs(10)))
.layer(CorsLayer::permissive())
.with_state(state);
// Layers apply bottom-up: CORS runs first on the way in.Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- the trait Handler is not implemented for fn
- Usually an extractor ordering problem or a return type that is not IntoResponse. Check that any body extractor is the final parameter.
- Requests hang
- A handler is blocking the runtime. Move synchronous work into spawn_blocking.
Best practices
- Put body-consuming extractors (Json, Form) last in the parameter list.
- Implement IntoResponse for one application error type instead of returning StatusCode everywhere.
- Add TimeoutLayer and TraceLayer early; both are hard to retrofit meaningfully.
- Share state with State<T> rather than global statics.
Background
Why it exists, and what it was reacting to.
Axum replaced earlier Tokio-adjacent frameworks by using extractors — handler parameters whose types declare what to pull from the request — and by adopting Tower middleware, so the ecosystem is shared rather than framework-specific.
