Skip to content

Rust Web Services Best Practices

Objective: Master senior-level Rust web service patterns for production systems. When you need to build scalable web services, when you want to leverage Rust's performance for web applications, when you need enterprise-grade web service patternsโ€”these best practices become your weapon of choice.

Core Principles

  • Performance: Leverage Rust's zero-cost abstractions for web services
  • Type Safety: Use Rust's type system for API safety
  • Async Programming: Build high-performance async web services
  • Middleware: Implement reusable middleware patterns
  • Error Handling: Comprehensive error handling for web services

Web Framework Patterns

Actix Web Patterns

// rust/01-actix-patterns.rs

/*
Actix Web patterns and best practices for Rust
*/

use actix_web::{web, App, HttpServer, HttpResponse, Result, middleware};
use serde::{Deserialize, Serialize};
use std::sync::Mutex;

/// User data structure.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub id: u64,
    pub name: String,
    pub email: String,
}

/// User service for managing users.
pub struct UserService {
    users: Mutex<Vec<User>>,
    next_id: Mutex<u64>,
}

impl UserService {
    pub fn new() -> Self {
        Self {
            users: Mutex::new(Vec::new()),
            next_id: Mutex::new(1),
        }
    }

    pub fn create_user(&self, name: String, email: String) -> Result<User, String> {
        let mut users = self.users.lock().unwrap();
        let mut next_id = self.next_id.lock().unwrap();

        let user = User {
            id: *next_id,
            name,
            email,
        };

        *next_id += 1;
        users.push(user.clone());
        Ok(user)
    }

    pub fn get_user(&self, id: u64) -> Result<Option<User>, String> {
        let users = self.users.lock().unwrap();
        Ok(users.iter().find(|u| u.id == id).cloned())
    }

    pub fn get_all_users(&self) -> Result<Vec<User>, String> {
        let users = self.users.lock().unwrap();
        Ok(users.clone())
    }

    pub fn update_user(&self, id: u64, name: Option<String>, email: Option<String>) -> Result<Option<User>, String> {
        let mut users = self.users.lock().unwrap();

        if let Some(user) = users.iter_mut().find(|u| u.id == id) {
            if let Some(name) = name {
                user.name = name;
            }
            if let Some(email) = email {
                user.email = email;
            }
            Ok(Some(user.clone()))
        } else {
            Ok(None)
        }
    }

    pub fn delete_user(&self, id: u64) -> Result<bool, String> {
        let mut users = self.users.lock().unwrap();
        let initial_len = users.len();
        users.retain(|u| u.id != id);
        Ok(users.len() < initial_len)
    }
}

/// Request/Response structures.
#[derive(Deserialize)]
pub struct CreateUserRequest {
    pub name: String,
    pub email: String,
}

#[derive(Deserialize)]
pub struct UpdateUserRequest {
    pub name: Option<String>,
    pub email: Option<String>,
}

/// Handler functions.
pub async fn create_user(
    data: web::Data<UserService>,
    req: web::Json<CreateUserRequest>,
) -> Result<HttpResponse, actix_web::Error> {
    let user = data.create_user(req.name.clone(), req.email.clone())
        .map_err(|e| actix_web::error::ErrorBadRequest(e))?;

    Ok(HttpResponse::Created().json(user))
}

pub async fn get_user(
    data: web::Data<UserService>,
    path: web::Path<u64>,
) -> Result<HttpResponse, actix_web::Error> {
    let user = data.get_user(path.into_inner())
        .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

    match user {
        Some(user) => Ok(HttpResponse::Ok().json(user)),
        None => Ok(HttpResponse::NotFound().json("User not found")),
    }
}

pub async fn get_all_users(
    data: web::Data<UserService>,
) -> Result<HttpResponse, actix_web::Error> {
    let users = data.get_all_users()
        .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

    Ok(HttpResponse::Ok().json(users))
}

pub async fn update_user(
    data: web::Data<UserService>,
    path: web::Path<u64>,
    req: web::Json<UpdateUserRequest>,
) -> Result<HttpResponse, actix_web::Error> {
    let user = data.update_user(path.into_inner(), req.name.clone(), req.email.clone())
        .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

    match user {
        Some(user) => Ok(HttpResponse::Ok().json(user)),
        None => Ok(HttpResponse::NotFound().json("User not found")),
    }
}

pub async fn delete_user(
    data: web::Data<UserService>,
    path: web::Path<u64>,
) -> Result<HttpResponse, actix_web::Error> {
    let deleted = data.delete_user(path.into_inner())
        .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

    if deleted {
        Ok(HttpResponse::NoContent().finish())
    } else {
        Ok(HttpResponse::NotFound().json("User not found"))
    }
}

/// Application configuration.
pub fn configure_app(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/api/v1")
            .service(
                web::resource("/users")
                    .route(web::get().to(get_all_users))
                    .route(web::post().to(create_user))
            )
            .service(
                web::resource("/users/{id}")
                    .route(web::get().to(get_user))
                    .route(web::put().to(update_user))
                    .route(web::delete().to(delete_user))
            )
    );
}

/// Main application setup.
pub async fn create_app() -> std::io::Result<()> {
    let user_service = web::Data::new(UserService::new());

    HttpServer::new(move || {
        App::new()
            .app_data(user_service.clone())
            .wrap(middleware::Logger::default())
            .wrap(middleware::DefaultHeaders::new().header("X-Version", "1.0"))
            .configure(configure_app)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_user_service() {
        let service = UserService::new();

        let user = service.create_user("John".to_string(), "john@example.com".to_string()).unwrap();
        assert_eq!(user.name, "John");
        assert_eq!(user.email, "john@example.com");

        let retrieved = service.get_user(user.id).unwrap().unwrap();
        assert_eq!(retrieved.name, "John");
    }
}

Axum Patterns

// rust/02-axum-patterns.rs

/*
Axum patterns and best practices for Rust
*/

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

/// User data structure.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub id: u64,
    pub name: String,
    pub email: String,
}

/// Application state.
pub struct AppState {
    pub users: Mutex<Vec<User>>,
    pub next_id: Mutex<u64>,
}

impl AppState {
    pub fn new() -> Self {
        Self {
            users: Mutex::new(Vec::new()),
            next_id: Mutex::new(1),
        }
    }
}

/// Request/Response structures.
#[derive(Deserialize)]
pub struct CreateUserRequest {
    pub name: String,
    pub email: String,
}

#[derive(Deserialize)]
pub struct UpdateUserRequest {
    pub name: Option<String>,
    pub email: Option<String>,
}

#[derive(Deserialize)]
pub struct UserQuery {
    pub limit: Option<usize>,
    pub offset: Option<usize>,
}

/// Handler functions.
pub async fn create_user(
    State(state): State<Arc<AppState>>,
    Json(req): Json<CreateUserRequest>,
) -> Result<Json<User>, StatusCode> {
    let mut users = state.users.lock().await;
    let mut next_id = state.next_id.lock().await;

    let user = User {
        id: *next_id,
        name: req.name,
        email: req.email,
    };

    *next_id += 1;
    users.push(user.clone());

    Ok(Json(user))
}

pub async fn get_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<u64>,
) -> Result<Json<User>, StatusCode> {
    let users = state.users.lock().await;

    match users.iter().find(|u| u.id == id) {
        Some(user) => Ok(Json(user.clone())),
        None => Err(StatusCode::NOT_FOUND),
    }
}

pub async fn get_all_users(
    State(state): State<Arc<AppState>>,
    Query(query): Query<UserQuery>,
) -> Result<Json<Vec<User>>, StatusCode> {
    let users = state.users.lock().await;
    let mut result = users.clone();

    if let Some(offset) = query.offset {
        result = result.into_iter().skip(offset).collect();
    }

    if let Some(limit) = query.limit {
        result = result.into_iter().take(limit).collect();
    }

    Ok(Json(result))
}

pub async fn update_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<u64>,
    Json(req): Json<UpdateUserRequest>,
) -> Result<Json<User>, StatusCode> {
    let mut users = state.users.lock().await;

    match users.iter_mut().find(|u| u.id == id) {
        Some(user) => {
            if let Some(name) = req.name {
                user.name = name;
            }
            if let Some(email) = req.email {
                user.email = email;
            }
            Ok(Json(user.clone()))
        }
        None => Err(StatusCode::NOT_FOUND),
    }
}

pub async fn delete_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<u64>,
) -> Result<StatusCode, StatusCode> {
    let mut users = state.users.lock().await;
    let initial_len = users.len();
    users.retain(|u| u.id != id);

    if users.len() < initial_len {
        Ok(StatusCode::NO_CONTENT)
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}

/// Application setup.
pub fn create_app() -> Router {
    let state = Arc::new(AppState::new());

    Router::new()
        .route("/api/v1/users", get(get_all_users).post(create_user))
        .route("/api/v1/users/:id", get(get_user).put(update_user).delete(delete_user))
        .with_state(state)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_app_state() {
        let state = Arc::new(AppState::new());

        let req = CreateUserRequest {
            name: "John".to_string(),
            email: "john@example.com".to_string(),
        };

        let result = create_user(State(state), Json(req)).await;
        assert!(result.is_ok());
    }
}

Warp Patterns

// rust/03-warp-patterns.rs

/*
Warp patterns and best practices for Rust
*/

use warp::{
    Filter, Rejection, Reply,
    filters::cors::CorsForbidden,
    http::StatusCode,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::Mutex;

/// User data structure.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub id: u64,
    pub name: String,
    pub email: String,
}

/// Application state.
pub struct AppState {
    pub users: Mutex<Vec<User>>,
    pub next_id: Mutex<u64>,
}

impl AppState {
    pub fn new() -> Self {
        Self {
            users: Mutex::new(Vec::new()),
            next_id: Mutex::new(1),
        }
    }
}

/// Request/Response structures.
#[derive(Deserialize)]
pub struct CreateUserRequest {
    pub name: String,
    pub email: String,
}

#[derive(Deserialize)]
pub struct UpdateUserRequest {
    pub name: Option<String>,
    pub email: Option<String>,
}

/// Custom error type.
#[derive(Debug)]
pub enum AppError {
    UserNotFound,
    InvalidRequest,
    InternalError,
}

impl warp::reject::Reject for AppError {}

/// Handler functions.
pub async fn create_user(
    req: CreateUserRequest,
    state: Arc<AppState>,
) -> Result<impl Reply, Rejection> {
    let mut users = state.users.lock().await;
    let mut next_id = state.next_id.lock().await;

    let user = User {
        id: *next_id,
        name: req.name,
        email: req.email,
    };

    *next_id += 1;
    users.push(user.clone());

    Ok(warp::reply::json(&user))
}

pub async fn get_user(
    id: u64,
    state: Arc<AppState>,
) -> Result<impl Reply, Rejection> {
    let users = state.users.lock().await;

    match users.iter().find(|u| u.id == id) {
        Some(user) => Ok(warp::reply::json(user)),
        None => Err(warp::reject::custom(AppError::UserNotFound)),
    }
}

pub async fn get_all_users(
    state: Arc<AppState>,
) -> Result<impl Reply, Rejection> {
    let users = state.users.lock().await;
    Ok(warp::reply::json(&*users))
}

pub async fn update_user(
    id: u64,
    req: UpdateUserRequest,
    state: Arc<AppState>,
) -> Result<impl Reply, Rejection> {
    let mut users = state.users.lock().await;

    match users.iter_mut().find(|u| u.id == id) {
        Some(user) => {
            if let Some(name) = req.name {
                user.name = name;
            }
            if let Some(email) = req.email {
                user.email = email;
            }
            Ok(warp::reply::json(user))
        }
        None => Err(warp::reject::custom(AppError::UserNotFound)),
    }
}

pub async fn delete_user(
    id: u64,
    state: Arc<AppState>,
) -> Result<impl Reply, Rejection> {
    let mut users = state.users.lock().await;
    let initial_len = users.len();
    users.retain(|u| u.id != id);

    if users.len() < initial_len {
        Ok(StatusCode::NO_CONTENT)
    } else {
        Err(warp::reject::custom(AppError::UserNotFound))
    }
}

/// Filter functions.
pub fn with_state(
    state: Arc<AppState>,
) -> impl Filter<Extract = (Arc<AppState>,), Error = std::convert::Infallible> + Clone {
    warp::any().map(move || state.clone())
}

pub fn json_body<T>() -> impl Filter<Extract = (T,), Error = warp::Rejection> + Clone
where
    T: for<'de> serde::Deserialize<'de> + Send,
{
    warp::body::content_length_limit(1024 * 16).and(warp::body::json())
}

/// Application setup.
pub fn create_app() -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
    let state = Arc::new(AppState::new());

    let cors = warp::cors()
        .allow_any_origin()
        .allow_headers(vec!["content-type"])
        .allow_methods(vec!["GET", "POST", "PUT", "DELETE"]);

    let create_user = warp::path("api")
        .and(warp::path("v1"))
        .and(warp::path("users"))
        .and(warp::post())
        .and(json_body::<CreateUserRequest>())
        .and(with_state(state.clone()))
        .and_then(create_user);

    let get_user = warp::path("api")
        .and(warp::path("v1"))
        .and(warp::path("users"))
        .and(warp::path::param::<u64>())
        .and(warp::get())
        .and(with_state(state.clone()))
        .and_then(get_user);

    let get_all_users = warp::path("api")
        .and(warp::path("v1"))
        .and(warp::path("users"))
        .and(warp::get())
        .and(with_state(state.clone()))
        .and_then(get_all_users);

    let update_user = warp::path("api")
        .and(warp::path("v1"))
        .and(warp::path("users"))
        .and(warp::path::param::<u64>())
        .and(warp::put())
        .and(json_body::<UpdateUserRequest>())
        .and(with_state(state.clone()))
        .and_then(update_user);

    let delete_user = warp::path("api")
        .and(warp::path("v1"))
        .and(warp::path("users"))
        .and(warp::path::param::<u64>())
        .and(warp::delete())
        .and(with_state(state.clone()))
        .and_then(delete_user);

    create_user
        .or(get_user)
        .or(get_all_users)
        .or(update_user)
        .or(delete_user)
        .with(cors)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_app_state() {
        let state = Arc::new(AppState::new());

        let req = CreateUserRequest {
            name: "John".to_string(),
            email: "john@example.com".to_string(),
        };

        let result = create_user(req, state).await;
        assert!(result.is_ok());
    }
}

TL;DR Runbook

Quick Start

// 1. Actix Web
use actix_web::{web, App, HttpServer, HttpResponse};

async fn handler() -> HttpResponse {
    HttpResponse::Ok().json("Hello, World!")
}

// 2. Axum
use axum::{Router, routing::get, Json};

async fn handler() -> Json<&'static str> {
    Json("Hello, World!")
}

// 3. Warp
use warp::Filter;

let routes = warp::path("hello")
    .map(|| "Hello, World!");

Essential Patterns

// Complete web services setup
pub fn setup_rust_web_services() {
    // 1. Web frameworks
    // 2. Async programming
    // 3. Middleware patterns
    // 4. Error handling
    // 5. State management
    // 6. Routing patterns
    // 7. Serialization
    // 8. Performance optimization

    println!("Rust web services setup complete!");
}

This guide provides the complete machinery for Rust web services. Each pattern includes implementation examples, web service strategies, and real-world usage patterns for enterprise web development.