What is Axum?
Axum is a web framework built by the Tokio team. It leverages the tower ecosystem for middleware, making it composable and interoperable. Axum has become the most popular Rust web framework due to its ergonomic API and tight integration with the Tokio async runtime.
Getting Started
// Cargo.toml
// [dependencies]
// axum = "0.7"
// tokio = { version = "1", features = ["full"] }
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"
// tower-http = { version = "0.5", features = ["cors", "trace"] }
// tracing = "0.1"
// tracing-subscriber = "0.3"
use axum::{
routing::{get, post},
Router, Json,
http::StatusCode,
};
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
struct HealthResponse {
status: String,
version: String,
}
async fn health() -> Json {
Json(HealthResponse {
status: "healthy".into(),
version: "1.0.0".into(),
})
}
async fn hello() -> &'static str {
"Hello, World!"
}
#[tokio::main]
async fn main() {
tracing_subscriber::init();
let app = Router::new()
.route("/", get(hello))
.route("/health", get(health));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await.unwrap();
println!("Listening on http://localhost:3000");
axum::serve(listener, app).await.unwrap();
}
Handlers and Extractors
use axum::{
extract::{Path, Query, State, Json},
http::StatusCode,
response::IntoResponse,
Router,
routing::{get, post, put, delete},
};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Clone)]
struct User {
id: u64,
name: String,
email: String,
}
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[derive(Deserialize)]
struct ListParams {
page: Option,
limit: Option,
}
// Path parameters
async fn get_user(Path(id): Path) -> impl IntoResponse {
Json(User { id, name: "Alice".into(), email: "alice@example.com".into() })
}
// JSON body
async fn create_user(Json(payload): Json) -> impl IntoResponse {
(StatusCode::CREATED, Json(User {
id: 1,
name: payload.name,
email: payload.email,
}))
}
// Query parameters
async fn list_users(Query(params): Query) -> impl IntoResponse {
let page = params.page.unwrap_or(1);
let limit = params.limit.unwrap_or(20);
Json(serde_json::json!({
"page": page,
"limit": limit,
"users": Vec::::new()
}))
}
// Multiple path parameters
async fn get_user_post(
Path((user_id, post_id)): Path<(u64, u64)>,
) -> impl IntoResponse {
Json(serde_json::json!({
"user_id": user_id,
"post_id": post_id
}))
}
fn user_routes() -> Router {
Router::new()
.route("/users", get(list_users).post(create_user))
.route("/users/:id", get(get_user))
.route("/users/:user_id/posts/:post_id", get(get_user_post))
}
State Management
use axum::{extract::State, Router, routing::get, Json};
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
struct AppState {
db: DatabasePool,
config: AppConfig,
cache: Arc>>,
}
#[derive(Clone)]
struct DatabasePool;
#[derive(Clone)]
struct AppConfig { pub app_name: String }
async fn with_state(State(state): State) -> Json {
let cache = state.cache.read().await;
Json(serde_json::json!({
"app": state.config.app_name,
"cache_size": cache.len()
}))
}
async fn update_cache(
State(state): State,
Json(data): Json>,
) -> &'static str {
let mut cache = state.cache.write().await;
for (k, v) in data {
cache.insert(k, v);
}
"Cache updated"
}
#[tokio::main]
async fn main() {
let state = AppState {
db: DatabasePool,
config: AppConfig { app_name: "MyApp".into() },
cache: Arc::new(RwLock::new(std::collections::HashMap::new())),
};
let app = Router::new()
.route("/state", axum::routing::get(with_state))
.route("/cache", axum::routing::post(update_cache))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Middleware with Tower
use axum::{Router, routing::get, middleware};
use tower_http::{
cors::{CorsLayer, Any},
trace::TraceLayer,
compression::CompressionLayer,
timeout::TimeoutLayer,
};
use std::time::Duration;
async fn handler() -> &'static str { "Hello!" }
fn build_app() -> Router {
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
Router::new()
.route("/", get(handler))
.layer(cors)
.layer(TraceLayer::new_for_http())
.layer(CompressionLayer::new())
.layer(TimeoutLayer::new(Duration::from_secs(30)))
}
// Axum vs Actix:
// Axum: Built by Tokio team, tower middleware, simpler API
// Actix: Slightly faster raw throughput, more mature, actor model
// Both are excellent — Axum has more momentum in 2024+
Key Takeaways
- ✅ Axum is built by the Tokio team with first-class async support and tower middleware
- ✅ Extractors (Path, Query, Json, State) parse request data with type safety
- ✅ State is shared via
with_state()— use Arc/RwLock for mutable shared state - ✅ Tower middleware provides CORS, tracing, compression, and timeouts
- ✅ Axum is now the most popular Rust web framework with strong ecosystem support