Skip to content
Vladimir Chavkov
Go back

Rust Web Development: Getting Started with Actix Web and Axum

Edit page

Rust’s web ecosystem has matured significantly. Two frameworks dominate: Actix Web and Axum. Both are production-ready, fast, and well-maintained. This post walks through building the same REST API in each, then compares their design philosophies and ecosystem.

The Same API in Both Frameworks

We’ll build a simple JSON API with a health check and a greeting endpoint.

Actix Web

use actix_web::{get, web, App, HttpServer, HttpResponse, Responder};
use serde::Serialize;
#[derive(Serialize)]
struct Greeting {
message: String,
}
#[get("/health")]
async fn health() -> impl Responder {
HttpResponse::Ok().json(serde_json::json!({"status": "ok"}))
}
#[get("/greet/{name}")]
async fn greet(path: web::Path<String>) -> impl Responder {
let name = path.into_inner();
HttpResponse::Ok().json(Greeting {
message: format!("Hello, {}!", name),
})
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(health)
.service(greet)
})
.bind("127.0.0.1:8080")?
.run()
.await
}

Axum

use axum::{extract::Path, routing::get, Json, Router};
use serde::Serialize;
#[derive(Serialize)]
struct Greeting {
message: String,
}
async fn health() -> Json<serde_json::Value> {
Json(serde_json::json!({"status": "ok"}))
}
async fn greet(Path(name): Path<String>) -> Json<Greeting> {
Json(Greeting {
message: format!("Hello, {}!", name),
})
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/health", get(health))
.route("/greet/{name}", get(greet));
let listener = tokio::net::TcpListener::bind("127.0.0.1:8080")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}

Both produce identical JSON responses. The differences are in how you structure the code.

Design Philosophy

Actix Web

Axum

Performance

Both frameworks consistently rank near the top of web framework benchmarks. In practice, the difference between them is negligible for most applications. Actix Web has historically edged ahead in raw throughput benchmarks, while Axum’s Tower integration can offer more efficient middleware composition.

Bottom line: Neither framework will be your bottleneck. Your database queries, network calls, and business logic will dominate latency.

Shared State

Both frameworks support dependency injection via shared application state.

Actix Web uses web::Data<T>:

let db_pool = create_pool().await;
App::new()
.app_data(web::Data::new(db_pool))
.service(handler)

Axum uses State<T> extractor:

let db_pool = create_pool().await;
Router::new()
.route("/users", get(list_users))
.with_state(db_pool)

Both are thread-safe and work with Arc under the hood.

Middleware and Ecosystem

FeatureActix WebAxum
CORSactix-cors cratetower-http::cors
CompressionBuilt-intower-http::compression
Rate limitingactix-governortower_governor
Authactix-identity, actix-sessionaxum-extra, custom Tower layers
WebSocketsBuilt-inBuilt-in
OpenAPIpaperclip, utoipautoipa, aide

Actix Web has more purpose-built middleware. Axum leverages the entire Tower ecosystem, which is shared with Tonic (gRPC), Hyper, and other Tokio-based libraries.

When to Choose Each

Choose Actix Web when:

Choose Axum when:

Getting Started

Both frameworks have excellent documentation:

Pick one, build something, and switch later if needed. The concepts transfer directly between them. Rust’s type system and ownership model apply equally regardless of framework choice.


Edit page
Share this post on:

Previous Post
Svelte vs React: A Practical Comparison for 2025
Next Post
Rust Ownership and Borrowing: The Mental Model You Need