Skip to content
rusthacks.dev
Web

Axum Versus Actix Web and How to Choose With Numbers

Axum and Actix Web are both fast and mature. The real differences are the runtime model, middleware ecosystem and ergonomics, and this post lays out how to decide.

3 min read
Summarize in AI
Flat illustration for the post “Axum Versus Actix Web and How to Choose With Numbers”, composed around web.

Choosing a framework on a benchmark chart is how teams end up with the fast framework that nobody likes writing. Axum and Actix Web are both well past the point where throughput decides anything for a typical service, so the useful differences are structural. This post lays them out and ends with a recommendation. It is a decision piece in the Axum production service cluster.

Axum 0.8, Actix Web 4, Rust 1.90.

The same handler in both

Axum:

rust
use axum::{extract::Path, routing::get, Json, Router};

async fn hello(Path(name): Path<String>) -> Json<String> {
    Json(format!("hello {name}"))
}

fn app() -> Router {
    Router::new().route("/hello/{name}", get(hello))
}

Actix Web:

rust
use actix_web::{get, web, App, HttpServer, Responder};

#[get("/hello/{name}")]
async fn hello(name: web::Path<String>) -> impl Responder {
    web::Json(format!("hello {}", name.into_inner()))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(hello))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

The logic is the same. The differences are around it.

What actually differs

AxisAxumActix Web
FoundationHyper and TowerOwn service layer on Tokio
MiddlewareTower Layer, from_fnOwn Transform trait, wrap_fn
HandlersSend futures, work stealing runtimePer worker runtime, handlers need not be Send
StateState<T> with Cloneweb::Data<T> shared via Arc
Routing macrosBuilder methodsBuilder or attribute macros
MaintenanceTokio projectActix community

The runtime model is the one that changes how code looks. In Actix each worker thread has its own runtime and its own copy of the app factory closure, so state shared across workers needs an explicit Arc. In Axum one multi thread runtime serves all requests, so every handler future is Send; the consequences are in Send and Sync.

The benchmark question

Actix Web results in public plaintext and JSON benchmarks are often at the top, and Axum is close behind. These tests measure the framework doing almost nothing. A handler that makes one PostgreSQL query spends orders of magnitude longer in the driver. We do not report a number for this post, because the useful figure is yours: load test your real endpoint, with a tool such as oha or wrk, and compare p99 latency.

Ecosystem fit

If you use tower-http for compression, CORS and tracing, or tonic for gRPC, Axum shares the same types, and layers compose across them. Actix has its own equivalents for most things, and some require the actix- prefixed crate. For authentication and sessions, the Axum side leans on extractors and axum-extra; the Actix side has actix-session and actix-identity.

So which one

Choose Axum when the project is new, the team is mixed, or you value the Tower ecosystem. Choose Actix Web when the team already has production experience with it, or when you need its specific features. Do not migrate a working Actix service for performance.

When this advice flips

Edge cases: if you must run handlers that are not Send (some FFI handles), Actix's model is friendlier; if you want a single binary serving gRPC and HTTP on one port, Axum with tonic is easier. Each project documents its design in its own repository: axum on GitHub and actix-web on GitHub.

Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.