Skip to content

What it is

Actix Web is a mature, extremely fast Rust web framework with an extractor-based API, built-in middleware and first-class WebSocket support.

Handlers are async functions with extractors, registered on an App inside an HttpServer. The App factory runs once per worker thread.

Installation

cargo add actix-web

Getting started

The smallest useful thing you can do with it, and what each part means.

Server, routes and shared state
use actix_web::{web, App, HttpServer, HttpResponse, get};

#[get("/books/{id}")]
async fn get_book(
    path: web::Path<i64>,
    db: web::Data<Pool>,
) -> actix_web::Result<HttpResponse> {
    match db.find(path.into_inner()).await {
        Ok(Some(book)) => Ok(HttpResponse::Ok().json(book)),
        Ok(None) => Ok(HttpResponse::NotFound().finish()),
        Err(_) => Ok(HttpResponse::InternalServerError().finish()),
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let pool = web::Data::new(create_pool().await);

    HttpServer::new(move || {
        // This closure runs per worker thread.
        App::new().app_data(pool.clone()).service(get_book)
    })
    .bind(("0.0.0.0", 8080))?
    .run()
    .await
}
Create web::Data outside the factory closure and clone it in. Constructing it inside gives each worker its own pool, which is a common and expensive mistake.

Advanced usage

Where the library earns its place over a simpler alternative.

Middleware and scopes
use actix_web::middleware::{Logger, Compress, NormalizePath};

App::new()
    .wrap(Logger::default())
    .wrap(Compress::default())
    .wrap(NormalizePath::trim())
    .service(
        web::scope("/api/v1")
            .wrap(HttpAuthentication::bearer(validator))
            .service(get_book)
            .service(create_book),
    )
    .default_service(web::to(|| HttpResponse::NotFound()))
Scopes group routes under a prefix with their own middleware, so authentication applies to a whole section rather than being repeated per route.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

App data is not configured
app_data was not registered, or the extractor type does not match exactly — web::Data<Pool> and Pool are different types to the extractor.
Throughput collapses under load
A handler is blocking a worker. Wrap synchronous calls in web::block.

Best practices

  • Build shared state once outside the App factory and clone the handle inside it.
  • Use scopes to apply middleware to a group of routes rather than individually.
  • Never block in a handler; use web::block for synchronous work.
  • Prefer Axum for new projects wanting the Tower ecosystem; choose Actix for raw throughput and maturity.

Background

Why it exists, and what it was reacting to.

Long a fixture at the top of web framework benchmarks, Actix Web originally built on the actix actor system but now uses it only for WebSockets, making the common path considerably simpler.