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
- Uses its own actor-based runtime (built on Tokio under the hood).
- Attribute macros (
#[get("/path")]) define routes directly on handler functions. App::new().service(handler)registers routes via the service pattern.- Mature ecosystem with built-in middleware for sessions, CORS, compression, and static files.
- Slightly more opinionated about application structure.
Axum
- Built directly on Tokio and Tower, inheriting the Tower middleware ecosystem.
- Routes are plain async functions — no macros required.
Router::new().route("/path", get(handler))uses a builder pattern.- Extractors are function arguments: the type system determines what data to extract.
- Less opinionated, more composable, closely integrated with the broader Tokio ecosystem.
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
| Feature | Actix Web | Axum |
|---|---|---|
| CORS | actix-cors crate | tower-http::cors |
| Compression | Built-in | tower-http::compression |
| Rate limiting | actix-governor | tower_governor |
| Auth | actix-identity, actix-session | axum-extra, custom Tower layers |
| WebSockets | Built-in | Built-in |
| OpenAPI | paperclip, utoipa | utoipa, 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:
- You want a batteries-included framework with mature, Actix-specific middleware.
- You’re building a traditional web application with sessions and server-rendered content.
- You prefer attribute macros for route definitions.
Choose Axum when:
- You’re already invested in the Tokio/Tower ecosystem.
- You want maximum composability and minimal framework magic.
- You’re building microservices alongside gRPC (Tonic) or other Tower-based services.
- You prefer explicit routing over macros.
Getting Started
Both frameworks have excellent documentation:
- Actix Web: actix.rs
- Axum: docs.rs/axum
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.