Actix-web
Actix-web is a mature, high-performance HTTP framework with its own runtime model - a strong alternative to Axum for teams that want it.
Recipe
Quick-reference recipe card - copy-paste ready.
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateItem { name: String }
#[get("/health")]
async fn health() -> impl Responder { "ok" }
#[post("/items")]
async fn create(body: web::Json<CreateItem>) -> HttpResponse {
HttpResponse::Created().json(serde_json::json!({ "name": body.name }))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(health).service(create))
.bind(("127.0.0.1", 8080))?
.run()
.await
}When to reach for this: You need a battle-tested framework with built-in extractors, testing utilities, and a large ecosystem - and your team is comfortable outside the Tower-only stack.
Working Example
use actix_web::{delete, get, post, web, App, HttpResponse, HttpServer};
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
#[derive(Serialize, Clone)]
struct Item { id: u64, name: String }
#[derive(Deserialize)]
struct NewItem { name: String }
struct AppState { items: Mutex<Vec<Item>> }
#[get("/items")]
async fn list(data: web::Data<AppState>) -> web::Json<Vec<Item>> {
web::Json(data.items.lock().unwrap().clone())
}
#[post("/items")]
async fn create(data: web::Data<AppState>, body: web::Json<NewItem>) -> HttpResponse {
let mut items = data.items.lock().unwrap();
let item = Item { id: items.len() as u64 + 1, name: body.name.clone() };
items.push(item.clone());
HttpResponse::Created().json(item)
}
#[delete("/items/{id}")]
async fn remove(data: web::Data<AppState>, path: web::Path<u64>) -> HttpResponse {
let mut items = data.items.lock().unwrap();
if let Some(pos) = items.iter().position(|i| i.id == *path) {
items.remove(pos);
HttpResponse::NoContent().finish()
} else {
HttpResponse::NotFound().finish()
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let state = web::Data::new(AppState { items: Mutex::new(vec![]) });
HttpServer::new(move || {
App::new()
.app_data(state.clone())
.service(list)
.service(create)
.service(remove)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}What this demonstrates:
- Attribute macros
#[get],#[post]define routes on handler functions. web::Data<T>shares state across worker threads.web::Jsonfor request and response serialization.HttpServerspawns multiple workers for parallelism.
Deep Dive
How It Works
- Actix-web builds on the Actix actor system for concurrency (though handlers are async).
HttpServerclones theAppfactory per worker thread.- Extractors like
web::Path,web::Query,web::Jsonmirror Axum ergonomics.
Axum vs Actix-web
| Topic | Axum | Actix-web |
|---|---|---|
| Ecosystem | Tower layers everywhere | Actix middleware |
| State | State<T> + Clone | web::Data<T> + Arc |
| Learning curve | Tower concepts | Self-contained docs |
| Performance | Excellent | Excellent |
Rust Notes
// Scope routes under a prefix
use actix_web::web::scope;
App::new().service(scope("/api/v1").service(list).service(create));Gotchas
- Mutex in async handlers -
std::sync::Mutexacross await points can deadlock. Fix: Usetokio::sync::Mutexor keep lock scopes short. - App factory cloning - Expensive setup in
HttpServer::newclosure runs per worker. Fix: Create sharedweb::Dataoutside the closure. - Mixing runtimes - Libraries expecting pure Tokio may need adapters. Fix: Prefer Tokio-native crates (
sqlx,reqwest). - Different middleware model - Tower layers do not drop in directly. Fix: Use
actix-webmiddleware or wrap at reverse proxy. - Test client differences -
TestRequestAPI differs from Axumoneshot. Fix: Standardize on one framework per service.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Axum | New projects wanting Tower composability | Team has deep Actix expertise and existing code |
| Rocket | Rapid prototyping with macros | Production services needing max ecosystem choice |
| warp | Legacy hyper 0.14 stacks | Greenfield projects |
FAQs
Is Actix still maintained?
Yes - active releases; widely used in production.
Actix actors required?
No for typical HTTP handlers - async functions suffice.
WebSockets in Actix?
actix-web-actors and actix-ws support upgrades.
sqlx with Actix?
Works via web::Data<PgPool> and #[get] handlers.
Deploy behind nginx?
Same as Axum - reverse proxy to bound port, pass X-Forwarded-* headers.
OpenAPI?
paperclip or manual schemas - not built-in.
Graceful shutdown?
HttpServer::run handles signals; configure timeout for drain.
Migrate Actix to Axum?
Rewrite routes and middleware; share domain logic crates unchanged.
Performance tuning?
Match worker count to CPU cores; profile before micro-optimizing framework choice.
Which to choose?
Default new services to Axum; keep Actix when migration cost exceeds benefit.
Related
- Web Backends Basics - Axum-first intro
- Axum Routing & Handlers - Closest Axum equivalent
- Middleware & Tower - Axum middleware model
- Testing Web Services - Test patterns
- Web Backends Best Practices - Framework-agnostic rules
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.