Web Development

Building a REST API with Actix Web: From Zero to Production

When most developers think about building web APIs, they reach for Node.js, Python’s FastAPI, or Go’s net/http. But Rust’s web frameworks have matured significantly, and Actix Web sits at the top of the TechEmpower benchmarks for a reason.

Let’s build a production API from scratch.

Project Setup🔗

Start with a new Cargo project and add the dependencies:

[dependencies]
actix-web = "4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }

Defining the Application🔗

The entry point is straightforward. Actix Web uses a builder pattern to compose your server:

use actix_web::{web, App, HttpServer, HttpResponse};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/health", web::get().to(health_check))
            .service(
                web::scope("/api/v1")
                    .route("/users", web::get().to(list_users))
                    .route("/users", web::post().to(create_user))
            )
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Request Handlers and JSON🔗

Actix Web’s extractors automatically deserialize request data:

use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct CreateUserRequest {
    name: String,
    email: String,
}

#[derive(Serialize)]
struct UserResponse {
    id: u64,
    name: String,
    email: String,
}

async fn create_user(body: web::Json<CreateUserRequest>) -> HttpResponse {
    // In production, this would insert into a database
    let user = UserResponse {
        id: 1,
        name: body.name.clone(),
        email: body.email.clone(),
    };
    HttpResponse::Created().json(user)
}

async fn health_check() -> HttpResponse {
    HttpResponse::Ok().json(serde_json::json!({"status": "healthy"}))
}

The web::Json<T> extractor handles content-type validation, deserialization, and error responses automatically. If the request body doesn’t match the expected schema, Actix returns a 400 status code with a descriptive error message.

Middleware for Cross-Cutting Concerns🔗

Middleware in Actix Web wraps handlers to add behavior like logging, authentication, or CORS:

use actix_web::middleware;

HttpServer::new(|| {
    App::new()
        .wrap(middleware::Logger::default())
        .wrap(middleware::Compress::default())
        .route("/health", web::get().to(health_check))
})
How does middleware ordering work?

Middleware executes in the order it is registered (top to bottom) for requests, and in reverse order for responses. Place authentication middleware before your routes so unauthorized requests are rejected early without consuming downstream resources.

Why Actix Web for Production?🔗

Three factors set Actix apart:

  1. Performance: Consistently ranks in the top tier of the TechEmpower Framework Benchmarks
  2. Safety: Rust’s type system prevents entire categories of bugs at compile time
  3. Ecosystem: Strong integration with Diesel (ORM), SQLx (async SQL), and Tower (middleware)

The Rust web ecosystem has crossed the threshold from experimental to production-ready. If you value correctness and performance, Actix Web deserves serious consideration for your next API project.

More Articles