1. rust
  2. /web
  3. /frameworks

Web Frameworks (Axum, Warp, Actix)

Rust offers several excellent web frameworks, each with different design philosophies and strengths. This guide covers the most popular frameworks with practical examples to help you choose the right one for your project.

Axum - Modern Async Web Framework

Axum is a modern, ergonomic web framework built on top of Tower and Hyper, designed by the Tokio team.

Basic Axum Setup

First, add to Cargo.toml:

[dependencies]
axum = "0.7"
tokio = { version = "1.0", features = ["full"] }
tower = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Basic Axum application:

use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::Json,
    routing::{get, post, put, delete},
    Router,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

// Data structures
#[derive(Serialize, Deserialize, Clone)]
struct User {
    id: u32,
    name: String,
    email: String,
}

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

#[derive(Deserialize)]
struct QueryParams {
    name: Option<String>,
    limit: Option<u32>,
}

// Application state
type AppState = Arc<Mutex<HashMap<u32, User>>>;

// Route handlers
async fn root() -> &'static str {
    "Hello, Axum!"
}

async fn get_users(
    State(state): State<AppState>,
    Query(params): Query<QueryParams>,
) -> Json<Vec<User>> {
    let users = state.lock().unwrap();
    let mut result: Vec<User> = users.values().cloned().collect();
    
    // Filter by name if provided
    if let Some(name_filter) = params.name {
        result.retain(|user| user.name.contains(&name_filter));
    }
    
    // Limit results
    if let Some(limit) = params.limit {
        result.truncate(limit as usize);
    }
    
    Json(result)
}

async fn get_user(
    State(state): State<AppState>,
    Path(id): Path<u32>,
) -> Result<Json<User>, StatusCode> {
    let users = state.lock().unwrap();
    match users.get(&id) {
        Some(user) => Ok(Json(user.clone())),
        None => Err(StatusCode::NOT_FOUND),
    }
}

async fn create_user(
    State(state): State<AppState>,
    Json(payload): Json<CreateUser>,
) -> Result<Json<User>, StatusCode> {
    let mut users = state.lock().unwrap();
    let id = users.len() as u32 + 1;
    
    let user = User {
        id,
        name: payload.name,
        email: payload.email,
    };
    
    users.insert(id, user.clone());
    Ok(Json(user))
}

async fn update_user(
    State(state): State<AppState>,
    Path(id): Path<u32>,
    Json(payload): Json<CreateUser>,
) -> Result<Json<User>, StatusCode> {
    let mut users = state.lock().unwrap();
    
    match users.get_mut(&id) {
        Some(user) => {
            user.name = payload.name;
            user.email = payload.email;
            Ok(Json(user.clone()))
        }
        None => Err(StatusCode::NOT_FOUND),
    }
}

async fn delete_user(
    State(state): State<AppState>,
    Path(id): Path<u32>,
) -> StatusCode {
    let mut users = state.lock().unwrap();
    match users.remove(&id) {
        Some(_) => StatusCode::NO_CONTENT,
        None => StatusCode::NOT_FOUND,
    }
}

#[tokio::main]
async fn main() {
    // Initialize state
    let state = Arc::new(Mutex::new(HashMap::new()));
    
    // Build router
    let app = Router::new()
        .route("/", get(root))
        .route("/users", get(get_users).post(create_user))
        .route("/users/:id", get(get_user).put(update_user).delete(delete_user))
        .with_state(state);
    
    // Start server
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    
    println!("Server running on http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}

Advanced Axum Features

use axum::{
    extract::{rejection::JsonRejection, FromRequest, Request},
    http::{header, HeaderMap, Method, StatusCode},
    middleware::{self, Next},
    response::{IntoResponse, Response},
    Extension, Json,
};
use tower_http::{
    cors::{Any, CorsLayer},
    trace::TraceLayer,
};

// Custom extractor
#[derive(FromRequest)]
#[from_request(via(Json), rejection(ApiError))]
struct ValidatedJson<T>(T);

// Custom error type
#[derive(Debug)]
enum ApiError {
    JsonRejection(JsonRejection),
    ValidationError(String),
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            ApiError::JsonRejection(rejection) => {
                (StatusCode::BAD_REQUEST, rejection.to_string())
            }
            ApiError::ValidationError(msg) => {
                (StatusCode::UNPROCESSABLE_ENTITY, msg)
            }
        };
        
        (status, Json(serde_json::json!({
            "error": message
        }))).into_response()
    }
}

impl From<JsonRejection> for ApiError {
    fn from(rejection: JsonRejection) -> Self {
        Self::JsonRejection(rejection)
    }
}

// Authentication middleware
async fn auth_middleware(
    headers: HeaderMap,
    request: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let auth_header = headers
        .get(header::AUTHORIZATION)
        .and_then(|header| header.to_str().ok());
    
    match auth_header {
        Some(token) if token.starts_with("Bearer ") => {
            // Validate token here
            Ok(next.run(request).await)
        }
        _ => Err(StatusCode::UNAUTHORIZED),
    }
}

// CORS and middleware setup
fn create_app() -> Router {
    let cors = CorsLayer::new()
        .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
        .allow_headers(Any)
        .allow_origin(Any);
    
    Router::new()
        .route("/protected", get(protected_handler))
        .layer(middleware::from_fn(auth_middleware))
        .route("/public", get(public_handler))
        .layer(cors)
        .layer(TraceLayer::new_for_http())
}

async fn protected_handler() -> Json<serde_json::Value> {
    Json(serde_json::json!({
        "message": "This is a protected route"
    }))
}

async fn public_handler() -> Json<serde_json::Value> {
    Json(serde_json::json!({
        "message": "This is a public route"
    }))
}

Warp - Composable Web Framework

Warp is a fast, composable web framework focusing on filters and functional composition.

Basic Warp Setup

Add to Cargo.toml:

[dependencies]
warp = "0.3"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Basic Warp application:

use warp::{Filter, Rejection, Reply};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

#[derive(Serialize, Deserialize, Clone)]
struct User {
    id: u32,
    name: String,
    email: String,
}

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

type Users = Arc<Mutex<HashMap<u32, User>>>;

// Helper function to create state filter
fn with_users(users: Users) -> impl Filter<Extract = (Users,), Error = std::convert::Infallible> + Clone {
    warp::any().map(move || users.clone())
}

// Route handlers
async fn get_users(users: Users) -> Result<impl Reply, Rejection> {
    let users = users.lock().unwrap();
    let users_list: Vec<User> = users.values().cloned().collect();
    Ok(warp::reply::json(&users_list))
}

async fn get_user(id: u32, users: Users) -> Result<impl Reply, Rejection> {
    let users = users.lock().unwrap();
    match users.get(&id) {
        Some(user) => Ok(warp::reply::json(user)),
        None => Err(warp::reject::not_found()),
    }
}

async fn create_user(user: CreateUser, users: Users) -> Result<impl Reply, Rejection> {
    let mut users_map = users.lock().unwrap();
    let id = users_map.len() as u32 + 1;
    
    let new_user = User {
        id,
        name: user.name,
        email: user.email,
    };
    
    users_map.insert(id, new_user.clone());
    Ok(warp::reply::with_status(
        warp::reply::json(&new_user),
        warp::http::StatusCode::CREATED,
    ))
}

async fn update_user(id: u32, user: CreateUser, users: Users) -> Result<impl Reply, Rejection> {
    let mut users_map = users.lock().unwrap();
    
    match users_map.get_mut(&id) {
        Some(existing_user) => {
            existing_user.name = user.name;
            existing_user.email = user.email;
            Ok(warp::reply::json(existing_user))
        }
        None => Err(warp::reject::not_found()),
    }
}

async fn delete_user(id: u32, users: Users) -> Result<impl Reply, Rejection> {
    let mut users_map = users.lock().unwrap();
    match users_map.remove(&id) {
        Some(_) => Ok(warp::reply::with_status(
            warp::reply(),
            warp::http::StatusCode::NO_CONTENT,
        )),
        None => Err(warp::reject::not_found()),
    }
}

#[tokio::main]
async fn main() {
    let users = Arc::new(Mutex::new(HashMap::new()));
    
    // GET /users
    let users_list = warp::path("users")
        .and(warp::path::end())
        .and(warp::get())
        .and(with_users(users.clone()))
        .and_then(get_users);
    
    // GET /users/:id
    let users_get = warp::path("users")
        .and(warp::path::param::<u32>())
        .and(warp::path::end())
        .and(warp::get())
        .and(with_users(users.clone()))
        .and_then(get_user);
    
    // POST /users
    let users_create = warp::path("users")
        .and(warp::path::end())
        .and(warp::post())
        .and(warp::body::json())
        .and(with_users(users.clone()))
        .and_then(create_user);
    
    // PUT /users/:id
    let users_update = warp::path("users")
        .and(warp::path::param::<u32>())
        .and(warp::path::end())
        .and(warp::put())
        .and(warp::body::json())
        .and(with_users(users.clone()))
        .and_then(update_user);
    
    // DELETE /users/:id
    let users_delete = warp::path("users")
        .and(warp::path::param::<u32>())
        .and(warp::path::end())
        .and(warp::delete())
        .and(with_users(users.clone()))
        .and_then(delete_user);
    
    // Combine all routes
    let routes = users_list
        .or(users_get)
        .or(users_create)
        .or(users_update)
        .or(users_delete)
        .with(warp::cors().allow_any_origin())
        .with(warp::log("api"));
    
    println!("Server running on http://localhost:3030");
    warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}

Advanced Warp Features

use warp::{
    filters::header::headers_cloned,
    http::{header::HeaderMap, Method, StatusCode},
    Filter, Rejection, Reply,
};

// Custom rejection types
#[derive(Debug)]
struct Unauthorized;
impl warp::reject::Reject for Unauthorized {}

#[derive(Debug)]
struct ValidationError {
    message: String,
}
impl warp::reject::Reject for ValidationError {}

// Authentication filter
fn with_auth() -> impl Filter<Extract = (String,), Error = Rejection> + Clone {
    headers_cloned()
        .map(|headers: HeaderMap| {
            headers
                .get("authorization")
                .and_then(|value| value.to_str().ok())
                .and_then(|value| {
                    if value.starts_with("Bearer ") {
                        Some(value[7..].to_string())
                    } else {
                        None
                    }
                })
        })
        .and_then(|token: Option<String>| async move {
            match token {
                Some(token) => {
                    // Validate token here
                    if token == "valid_token" {
                        Ok(token)
                    } else {
                        Err(warp::reject::custom(Unauthorized))
                    }
                }
                None => Err(warp::reject::custom(Unauthorized)),
            }
        })
}

// Validation filter
fn validate_user() -> impl Filter<Extract = (CreateUser,), Error = Rejection> + Clone {
    warp::body::json::<CreateUser>()
        .and_then(|user: CreateUser| async move {
            if user.name.is_empty() {
                Err(warp::reject::custom(ValidationError {
                    message: "Name cannot be empty".to_string(),
                }))
            } else if !user.email.contains('@') {
                Err(warp::reject::custom(ValidationError {
                    message: "Invalid email format".to_string(),
                }))
            } else {
                Ok(user)
            }
        })
}

// Error handling
async fn handle_rejection(err: Rejection) -> Result<impl Reply, std::convert::Infallible> {
    let code;
    let message;
    
    if err.is_not_found() {
        code = StatusCode::NOT_FOUND;
        message = "Not Found";
    } else if let Some(_) = err.find::<Unauthorized>() {
        code = StatusCode::UNAUTHORIZED;
        message = "Unauthorized";
    } else if let Some(validation_error) = err.find::<ValidationError>() {
        code = StatusCode::BAD_REQUEST;
        message = &validation_error.message;
    } else if let Some(_) = err.find::<warp::reject::MethodNotAllowed>() {
        code = StatusCode::METHOD_NOT_ALLOWED;
        message = "Method Not Allowed";
    } else {
        code = StatusCode::INTERNAL_SERVER_ERROR;
        message = "Internal Server Error";
    }
    
    let json = warp::reply::json(&serde_json::json!({
        "error": message
    }));
    
    Ok(warp::reply::with_status(json, code))
}

// Protected route
async fn protected_handler(token: String) -> Result<impl Reply, Rejection> {
    Ok(warp::reply::json(&serde_json::json!({
        "message": "Protected route accessed",
        "token": token
    })))
}

// Create app with advanced features
fn create_warp_app() -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
    let protected = warp::path("protected")
        .and(warp::get())
        .and(with_auth())
        .and_then(protected_handler);
    
    let validated_create = warp::path("users")
        .and(warp::post())
        .and(validate_user())
        .and_then(|user: CreateUser| async move {
            Ok::<_, Rejection>(warp::reply::json(&user))
        });
    
    protected
        .or(validated_create)
        .recover(handle_rejection)
        .with(warp::log("api"))
}

Actix-Web - High-Performance Framework

Actix-web is a powerful, pragmatic web framework built on the Actix actor system.

Basic Actix-Web Setup

Add to Cargo.toml:

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

Basic Actix-web application:

use actix_web::{
    web, App, HttpResponse, HttpServer, Result, middleware::Logger,
    HttpRequest, Responder,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Mutex;

#[derive(Serialize, Deserialize, Clone)]
struct User {
    id: u32,
    name: String,
    email: String,
}

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

#[derive(Deserialize)]
struct QueryParams {
    name: Option<String>,
    limit: Option<u32>,
}

// Application state
struct AppState {
    users: Mutex<HashMap<u32, User>>,
}

// Route handlers
async fn get_users(
    data: web::Data<AppState>,
    query: web::Query<QueryParams>,
) -> Result<impl Responder> {
    let users = data.users.lock().unwrap();
    let mut result: Vec<User> = users.values().cloned().collect();
    
    // Filter by name if provided
    if let Some(name_filter) = &query.name {
        result.retain(|user| user.name.contains(name_filter));
    }
    
    // Limit results
    if let Some(limit) = query.limit {
        result.truncate(limit as usize);
    }
    
    Ok(HttpResponse::Ok().json(result))
}

async fn get_user(
    path: web::Path<u32>,
    data: web::Data<AppState>,
) -> Result<impl Responder> {
    let id = path.into_inner();
    let users = data.users.lock().unwrap();
    
    match users.get(&id) {
        Some(user) => Ok(HttpResponse::Ok().json(user)),
        None => Ok(HttpResponse::NotFound().json(serde_json::json!({
            "error": "User not found"
        }))),
    }
}

async fn create_user(
    user_data: web::Json<CreateUser>,
    data: web::Data<AppState>,
) -> Result<impl Responder> {
    let mut users = data.users.lock().unwrap();
    let id = users.len() as u32 + 1;
    
    let user = User {
        id,
        name: user_data.name.clone(),
        email: user_data.email.clone(),
    };
    
    users.insert(id, user.clone());
    Ok(HttpResponse::Created().json(user))
}

async fn update_user(
    path: web::Path<u32>,
    user_data: web::Json<CreateUser>,
    data: web::Data<AppState>,
) -> Result<impl Responder> {
    let id = path.into_inner();
    let mut users = data.users.lock().unwrap();
    
    match users.get_mut(&id) {
        Some(user) => {
            user.name = user_data.name.clone();
            user.email = user_data.email.clone();
            Ok(HttpResponse::Ok().json(user.clone()))
        }
        None => Ok(HttpResponse::NotFound().json(serde_json::json!({
            "error": "User not found"
        }))),
    }
}

async fn delete_user(
    path: web::Path<u32>,
    data: web::Data<AppState>,
) -> Result<impl Responder> {
    let id = path.into_inner();
    let mut users = data.users.lock().unwrap();
    
    match users.remove(&id) {
        Some(_) => Ok(HttpResponse::NoContent().finish()),
        None => Ok(HttpResponse::NotFound().json(serde_json::json!({
            "error": "User not found"
        }))),
    }
}

async fn health_check() -> Result<impl Responder> {
    Ok(HttpResponse::Ok().json(serde_json::json!({
        "status": "healthy",
        "timestamp": chrono::Utc::now()
    })))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
    
    let app_state = web::Data::new(AppState {
        users: Mutex::new(HashMap::new()),
    });
    
    HttpServer::new(move || {
        App::new()
            .app_data(app_state.clone())
            .wrap(Logger::default())
            .route("/health", web::get().to(health_check))
            .service(
                web::scope("/api/v1")
                    .route("/users", web::get().to(get_users))
                    .route("/users", web::post().to(create_user))
                    .route("/users/{id}", web::get().to(get_user))
                    .route("/users/{id}", web::put().to(update_user))
                    .route("/users/{id}", web::delete().to(delete_user))
            )
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Advanced Actix-Web Features

use actix_web::{
    dev::{ServiceRequest, ServiceResponse},
    error::{ErrorUnauthorized, ErrorBadRequest},
    middleware::{from_fn, Next},
    web, Error, HttpMessage,
};

// Custom middleware
async fn auth_middleware(
    req: ServiceRequest,
    next: Next<impl MessageBody>,
) -> Result<ServiceResponse<impl MessageBody>, Error> {
    let auth_header = req.headers().get("Authorization");
    
    match auth_header {
        Some(header_value) => {
            if let Ok(auth_str) = header_value.to_str() {
                if auth_str.starts_with("Bearer ") {
                    let token = &auth_str[7..];
                    // Validate token here
                    if token == "valid_token" {
                        req.extensions_mut().insert(token.to_string());
                        return next.call(req).await;
                    }
                }
            }
            Err(ErrorUnauthorized("Invalid token"))
        }
        None => Err(ErrorUnauthorized("Missing authorization header")),
    }
}

// Custom extractor
struct AuthenticatedUser(String);

impl FromRequest for AuthenticatedUser {
    type Error = Error;
    type Future = Ready<Result<Self, Self::Error>>;
    
    fn from_request(req: &HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future {
        match req.extensions().get::<String>() {
            Some(token) => ready(Ok(AuthenticatedUser(token.clone()))),
            None => ready(Err(ErrorUnauthorized("Authentication required"))),
        }
    }
}

// Validation
#[derive(Deserialize, Validate)]
struct CreateUserValidated {
    #[validate(length(min = 1, message = "Name cannot be empty"))]
    name: String,
    #[validate(email(message = "Invalid email format"))]
    email: String,
}

async fn create_user_validated(
    user_data: web::Json<CreateUserValidated>,
) -> Result<impl Responder> {
    // Validation is handled by the Validate derive
    if let Err(validation_errors) = user_data.validate() {
        return Ok(HttpResponse::BadRequest().json(serde_json::json!({
            "error": "Validation failed",
            "details": validation_errors
        })));
    }
    
    // Process valid user data
    Ok(HttpResponse::Created().json(serde_json::json!({
        "message": "User created successfully"
    })))
}

// Protected route handler
async fn protected_route(user: AuthenticatedUser) -> Result<impl Responder> {
    Ok(HttpResponse::Ok().json(serde_json::json!({
        "message": "Protected route accessed",
        "user_token": user.0
    })))
}

// Error handling
async fn custom_error_handler(req: ServiceRequest, next: Next) -> Result<ServiceResponse, Error> {
    let result = next.call(req).await;
    
    // You can log errors, modify responses, etc.
    result
}

// Application configuration
fn configure_app(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/api")
            .wrap(from_fn(auth_middleware))
            .route("/protected", web::get().to(protected_route))
    )
    .service(
        web::scope("/public")
            .route("/users", web::post().to(create_user_validated))
    );
}

Rocket - Type-Safe Web Framework

Rocket focuses on type safety and ease of use with compile-time guarantees.

Basic Rocket Setup

Add to Cargo.toml:

[dependencies]
rocket = { version = "0.5", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }

Basic Rocket application:

#[macro_use] extern crate rocket;

use rocket::{State, serde::{Deserialize, Serialize, json::Json}};
use std::collections::HashMap;
use std::sync::Mutex;

#[derive(Serialize, Deserialize, Clone)]
#[serde(crate = "rocket::serde")]
struct User {
    id: u32,
    name: String,
    email: String,
}

#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct CreateUser {
    name: String,
    email: String,
}

type UserStore = Mutex<HashMap<u32, User>>;

// Route handlers
#[get("/")]
fn index() -> &'static str {
    "Hello, Rocket!"
}

#[get("/users")]
fn get_users(store: &State<UserStore>) -> Json<Vec<User>> {
    let users = store.lock().unwrap();
    let users_list: Vec<User> = users.values().cloned().collect();
    Json(users_list)
}

#[get("/users/<id>")]
fn get_user(id: u32, store: &State<UserStore>) -> Option<Json<User>> {
    let users = store.lock().unwrap();
    users.get(&id).map(|user| Json(user.clone()))
}

#[post("/users", data = "<user_data>")]
fn create_user(user_data: Json<CreateUser>, store: &State<UserStore>) -> Json<User> {
    let mut users = store.lock().unwrap();
    let id = users.len() as u32 + 1;
    
    let user = User {
        id,
        name: user_data.name.clone(),
        email: user_data.email.clone(),
    };
    
    users.insert(id, user.clone());
    Json(user)
}

#[put("/users/<id>", data = "<user_data>")]
fn update_user(
    id: u32,
    user_data: Json<CreateUser>,
    store: &State<UserStore>,
) -> Option<Json<User>> {
    let mut users = store.lock().unwrap();
    users.get_mut(&id).map(|user| {
        user.name = user_data.name.clone();
        user.email = user_data.email.clone();
        Json(user.clone())
    })
}

#[delete("/users/<id>")]
fn delete_user(id: u32, store: &State<UserStore>) -> Option<()> {
    let mut users = store.lock().unwrap();
    users.remove(&id).map(|_| ())
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(UserStore::new(HashMap::new()))
        .mount("/", routes![index])
        .mount("/api", routes![
            get_users,
            get_user,
            create_user,
            update_user,
            delete_user
        ])
}

Advanced Rocket Features

use rocket::{
    request::{FromRequest, Outcome},
    http::Status,
    Request, response::status,
    fairing::{Fairing, Info, Kind},
};

// Custom request guard
struct ApiKey(String);

#[derive(Debug)]
enum ApiKeyError {
    Missing,
    Invalid,
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for ApiKey {
    type Error = ApiKeyError;

    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        fn is_valid(key: &str) -> bool {
            key == "valid_api_key"
        }

        match req.headers().get_one("x-api-key") {
            None => Outcome::Error((Status::BadRequest, ApiKeyError::Missing)),
            Some(key) if is_valid(key) => Outcome::Success(ApiKey(key.to_string())),
            Some(_) => Outcome::Error((Status::Unauthorized, ApiKeyError::Invalid)),
        }
    }
}

// Protected route using custom guard
#[get("/protected")]
fn protected(_key: ApiKey) -> &'static str {
    "This is a protected route!"
}

// Custom fairing (middleware)
pub struct RequestCounter {
    count: std::sync::atomic::AtomicUsize,
}

#[rocket::async_trait]
impl Fairing for RequestCounter {
    fn info(&self) -> Info {
        Info {
            name: "Request Counter",
            kind: Kind::Request
        }
    }

    async fn on_request(&self, _request: &mut Request<'_>, _: &mut rocket::Data<'_>) {
        self.count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
        println!("Request count: {}", self.count.load(std::sync::atomic::Ordering::Relaxed));
    }
}

// Error catchers
#[catch(404)]
fn not_found() -> Json<serde_json::Value> {
    Json(serde_json::json!({
        "error": "Not found",
        "status": 404
    }))
}

#[catch(500)]
fn internal_error() -> Json<serde_json::Value> {
    Json(serde_json::json!({
        "error": "Internal server error",
        "status": 500
    }))
}

// Advanced rocket configuration
#[launch]
fn rocket_advanced() -> _ {
    rocket::build()
        .manage(UserStore::new(HashMap::new()))
        .attach(RequestCounter { count: std::sync::atomic::AtomicUsize::new(0) })
        .mount("/", routes![index, protected])
        .mount("/api", routes![get_users, create_user])
        .register("/", catchers![not_found, internal_error])
}

Framework Comparison

Performance Comparison

// Benchmark example (requires criterion)
use criterion::{black_box, criterion_group, criterion_main, Criterion};

fn benchmark_frameworks(c: &mut Criterion) {
    // This is a conceptual example - actual benchmarks would require
    // running servers and making HTTP requests
    
    c.bench_function("axum_simple_route", |b| {
        b.iter(|| {
            // Simulate Axum route processing
            black_box("Hello, Axum!")
        })
    });
    
    c.bench_function("warp_simple_route", |b| {
        b.iter(|| {
            // Simulate Warp route processing
            black_box("Hello, Warp!")
        })
    });
    
    c.bench_function("actix_simple_route", |b| {
        b.iter(|| {
            // Simulate Actix route processing
            black_box("Hello, Actix!")
        })
    });
}

criterion_group!(benches, benchmark_frameworks);
criterion_main!(benches);

Feature Comparison Table

FrameworkAsyncType SafetyLearning CurveEcosystemPerformance
AxumHighMediumGrowingHigh
WarpHighMedium-HighModerateHigh
Actix-webMediumMediumMatureVery High
RocketVery HighLow-MediumGrowingHigh

Choosing the Right Framework

When to Use Axum

  • Modern async applications: Built on Tower ecosystem
  • Microservices: Excellent for API-first applications
  • Type safety: Strong typing with good ergonomics
  • Learning Rust web development: Good balance of features and simplicity
// Axum is ideal for:
use axum::{Router, routing::get, Json};

async fn api_handler() -> Json<serde_json::Value> {
    Json(serde_json::json!({
        "message": "Clean, modern API design"
    }))
}

let app = Router::new()
    .route("/api", get(api_handler));

When to Use Warp

  • Functional programming style: Filter-based composition
  • Complex routing logic: Powerful filter combinators
  • High performance requirements: Zero-cost abstractions
  • Custom middleware: Flexible filter system
// Warp excels at:
let routes = warp::path("api")
    .and(warp::path("users"))
    .and(warp::get())
    .and(with_db())
    .and(with_auth())
    .and_then(get_users_handler);

When to Use Actix-web

  • High-performance applications: Battle-tested performance
  • Full-featured web applications: Rich middleware ecosystem
  • Mature projects: Stable API and extensive documentation
  • WebSocket support: Built-in WebSocket handling
// Actix-web is great for:
use actix_web::{web, App, HttpServer};

HttpServer::new(|| {
    App::new()
        .wrap(Logger::default())
        .service(
            web::scope("/api")
                .route("/users", web::get().to(get_users))
        )
})
.bind("127.0.0.1:8080")?
.run()
.await

When to Use Rocket

  • Rapid prototyping: Quick setup and development
  • Type safety: Compile-time guarantees
  • Learning-friendly: Clear, intuitive API
  • Code generation: Automatic route generation
// Rocket simplifies:
#[get("/users/<id>")]
fn get_user(id: u32) -> Option<Json<User>> {
    // Type-safe parameter extraction
    find_user(id).map(Json)
}

Best Practices

1. Error Handling

// Consistent error handling across frameworks
use thiserror::Error;

#[derive(Error, Debug)]
enum ApiError {
    #[error("User not found")]
    UserNotFound,
    #[error("Database error: {0}")]
    Database(String),
    #[error("Validation error: {0}")]
    Validation(String),
}

// Implement framework-specific error conversion
impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            ApiError::UserNotFound => (StatusCode::NOT_FOUND, "User not found"),
            ApiError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Database error"),
            ApiError::Validation(msg) => (StatusCode::BAD_REQUEST, &msg),
        };
        
        (status, Json(serde_json::json!({
            "error": message
        }))).into_response()
    }
}

2. Configuration Management

use serde::Deserialize;

#[derive(Deserialize)]
struct Config {
    database_url: String,
    jwt_secret: String,
    port: u16,
}

impl Config {
    fn from_env() -> Result<Self, config::ConfigError> {
        let mut cfg = config::Config::new();
        cfg.merge(config::Environment::new())?;
        cfg.try_into()
    }
}

3. Database Integration

// Example with SQLx (works with all frameworks)
use sqlx::{PgPool, FromRow};

#[derive(FromRow, Serialize)]
struct User {
    id: i32,
    name: String,
    email: String,
}

async fn get_users_from_db(pool: &PgPool) -> Result<Vec<User>, sqlx::Error> {
    sqlx::query_as!(User, "SELECT id, name, email FROM users")
        .fetch_all(pool)
        .await
}

4. Testing

#[cfg(test)]
mod tests {
    use super::*;
    
    #[tokio::test]
    async fn test_get_users() {
        // Framework-specific test setup
        let app = create_test_app().await;
        
        let response = app
            .oneshot(
                Request::builder()
                    .uri("/users")
                    .body(Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();
        
        assert_eq!(response.status(), StatusCode::OK);
    }
}

Conclusion

Choose your Rust web framework based on:

  • Performance needs: Actix-web for maximum performance
  • Type safety: Rocket for compile-time guarantees
  • Modern async: Axum for Tower ecosystem integration
  • Functional style: Warp for filter composition
  • Learning curve: Rocket for beginners, Warp for advanced users
  • Ecosystem: Actix-web for mature tooling

All frameworks offer excellent performance and safety. The choice often comes down to API style preference and specific project requirements. Start with Axum or Rocket for new projects, consider Actix-web for high-performance needs, and explore Warp for functional programming approaches.