Skip to content
Home
Rust Web Development: Actix, Rocket, and Axum

Rust Web Development: Actix, Rocket, and Axum

Rust Rust 7 min read 1459 words Beginner ExcellentWiki Editorial Team

Rust is gaining traction in web development thanks to its performance, safety guarantees, and excellent async ecosystem. Frameworks like Axum, Actix-web, and Rocket provide productive foundations for building web APIs and services that are fast, correct, and maintainable.

This guide introduces the three major Rust web frameworks with practical examples for each.

Choosing a Framework

FeatureAxumActix-webRocket
Async runtimeTokioTokioTokio (since 0.5)
RoutingExtractor-basedMacro + builderAttribute macros
MiddlewareTower ecosystemBuilt-in + TowerBuilt-in + fairings
DatabaseAny via TowerAny via ActorsAny via state
MaturityHigh (used by major orgs)Very highHigh
ErgonomicsExcellentGoodExcellent

Axum

Axum is built on top of Tokio, Tower, and Hyper. It is the newest of the three but has quickly become the most popular choice for new Rust web projects.

Basic API

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

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

type Db = Arc<Mutex<Vec<User>>>;

#[tokio::main]
async fn main() {
    let db: Db = Arc::new(Mutex::new(vec![
        User { id: 1, name: "Alice".into(), email: "alice@example.com".into() },
    ]));

    let app = Router::new()
        .route("/users", get(list_users).post(create_user))
        .route("/users/:id", get(get_user))
        .with_state(db);

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
---

async fn list_users(State(db): State<Db>) -> Json<Vec<User>> {
    let users = db.lock().await;
    Json(users.clone())
---

async fn get_user(
    State(db): State<Db>,
    Path(id): Path<u64>,
) -> Result<Json<User>, StatusCode> {
    let users = db.lock().await;
    users.iter()
        .find(|u| u.id == id)
        .map(|u| Json(u.clone()))
        .ok_or(StatusCode::NOT_FOUND)
---

async fn create_user(
    State(db): State<Db>,
    Json(input): Json<User>,
) -> (StatusCode, Json<User>) {
    let mut users = db.lock().await;
    let user = User {
        id: users.len() as u64 + 1,
        ..input
    };
    users.push(user.clone());
    (StatusCode::CREATED, Json(user))
---

Extractors

Axum uses extractors to pull data from requests:

use axum::extract::{Query, Path, Json, Extension, Headers};

// Query parameters
async fn search(Query(params): Query<HashMap<String, String>>) -> String {
    format!("Searching for {:?}", params)
---

// Path parameters
async fn user_detail(Path(id): Path<u64>) -> String {
    format!("User ID: {}", id)
---

// Multiple extractors
async fn update_user(
    Path(id): Path<u64>,
    Json(body): Json<UpdateUser>,
    Extension(auth): Extension<AuthUser>,
) -> impl IntoResponse {
    // ...
---

Middleware with Tower

Axum uses Tower for middleware. Tower provides reusable components like rate limiting, timeouts, and load balancing:

use tower_http::{
    cors::CorsLayer,
    trace::TraceLayer,
    compression::CompressionLayer,
    timeout::TimeoutLayer,
---;
use std::time::Duration;

let app = Router::new()
    .route("/api", get(handler))
    .layer(TraceLayer::new_for_http())
    .layer(CompressionLayer::new())
    .layer(CorsLayer::permissive())
    .layer(TimeoutLayer::new(Duration::from_secs(30)));

Actix-web

Actix-web is a mature, high-performance framework based on the Actix actor framework. It is known for its excellent benchmarks and long-standing ecosystem.

Basic API

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

#[derive(Serialize, Deserialize, Clone)]
struct User {
    id: u64,
    name: String,
---

struct AppState {
    users: Mutex<Vec<User>>,
---

async fn list_users(data: web::Data<AppState>) -> impl Responder {
    let users = data.users.lock().unwrap();
    HttpResponse::Ok().json(users.clone())
---

async fn create_user(
    data: web::Data<AppState>,
    body: web::Json<User>,
) -> impl Responder {
    let mut users = data.users.lock().unwrap();
    let user = User { id: users.len() as u64 + 1, ..body.into_inner() };
    users.push(user.clone());
    HttpResponse::Created().json(user)
---

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let state = web::Data::new(AppState {
        users: Mutex::new(vec![]),
    });

    HttpServer::new(move || {
        App::new()
            .app_data(state.clone())
            .route("/users", web::get().to(list_users))
            .route("/users", web::post().to(create_user))
    })
    .bind(("127.0.0.1", 3000))?
    .run()
    .await
---

Actors

Actix-web can use actors for state management, providing isolated message-passing concurrency:

use actix::prelude::*;

struct UserActor {
    users: Vec<User>,
---

impl Actor for UserActor {
    type Context = Context<Self>;
---

struct GetUser(u64);
impl Message for GetUser {
    type Result = Option<User>;
---

impl Handler<GetUser> for UserActor {
    type Result = Option<User>;

    fn handle(&mut self, msg: GetUser, _ctx: &mut Self::Context) -> Self::Result {
        self.users.iter().find(|u| u.id == msg.0).cloned()
    }
---

Rocket

Rocket focuses on developer experience with attribute macros and sensible defaults. It requires nightly Rust (though stable support is improving).

Basic API

#[macro_use] extern crate rocket;

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

#[derive(Serialize, Deserialize, Clone)]
struct User {
    id: u64,
    name: String,
---

type UserDb = Mutex<Vec<User>>;

#[get("/users")]
fn list_users(db: &State<UserDb>) -> Json<Vec<User>> {
    let users = db.lock().unwrap();
    Json(users.clone())
---

#[get("/users/<id>")]
fn get_user(db: &State<UserDb>, id: u64) -> Option<Json<User>> {
    let users = db.lock().unwrap();
    users.iter().find(|u| u.id == id).map(|u| Json(u.clone()))
---

#[post("/users", data = "<user>")]
fn create_user(db: &State<UserDb>, user: Json<User>) -> Json<User> {
    let mut users = db.lock().unwrap();
    let new_user = User { id: users.len() as u64 + 1, ..user.into_inner() };
    users.push(new_user.clone());
    Json(new_user)
---

#[launch]
fn rocket() -> _ {
    let db = Mutex::new(vec![
        User { id: 1, name: "Alice".into() },
    ]);

    rocket::build()
        .manage(db)
        .mount("/", routes![list_users, get_user, create_user])
---

Database Integration

SQLx (async, compile-time checked queries)

use sqlx::postgres::PgPoolOptions;

#[derive(sqlx::FromRow, Serialize)]
struct User {
    id: i64,
    name: String,
    email: String,
---

async fn get_users(pool: &sqlx::PgPool) -> Result<Vec<User>, sqlx::Error> {
    let users = sqlx::query_as::<_, User>("SELECT id, name, email FROM users")
        .fetch_all(pool)
        .await?;
    Ok(users)
---

// Usage in Axum:
async fn list_users(State(pool): State<sqlx::PgPool>) -> Result<Json<Vec<User>>, StatusCode> {
    let users = get_users(&pool).await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    Ok(Json(users))
---

Diesel (ORM-like, synchronous)

use diesel::prelude::*;
use diesel::pg::PgConnection;

#[derive(Queryable, Selectable, Insertable)]
#[diesel(table_name = crate::schema::users)]
struct User {
    id: i64,
    name: String,
    email: String,
---

fn get_all_users(conn: &mut PgConnection) -> QueryResult<Vec<User>> {
    use crate::schema::users::dsl::*;
    users.load::<User>(conn)
---

SeaORM (async ORM)

use sea_orm::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "users")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub name: String,
    pub email: String,
---

async fn find_all(db: &DatabaseConnection) -> Result<Vec<Model>, DbErr> {
    Entity::find().all(db).await
---

Best Practices

  • Use Axum for new projects — It has the best async ecosystem integration with Tower and Tokio
  • Structure your app with modules — Separate routes, handlers, models, and database layers
  • Use thiserror for error types — Return meaningful error types from handlers
  • Add observability — Use tracing for structured logging and tower-http trace middleware
  • Validate input — Use validator or garde for request validation
  • Database connection pooling — Reuse connections with sqlx::PgPool or deadpool
  • Rate limiting — Use tower-governor or actix-governor

Summary

Rust web development is production-ready. Axum provides excellent async integration with the Tower ecosystem. Actix-web offers mature performance and actor-based concurrency. Rocket delivers the best developer experience with attribute macros. Choose Axum for new projects — it has the most momentum, best ecosystem, and a design that scales from prototypes to production services.

Web Frameworks Compared

Rust’s web framework ecosystem has matured significantly. Actix-Web offers the highest raw performance with a handler-based architecture. Axum, built on Tokio and Tower, provides a modular middleware system and deep integration with the Tokio ecosystem. Rocket emphasizes ergonomics with attribute macros for routing and request handling. Warp uses a combinator-based approach for composable filters. For most new projects, Axum is the recommended starting point due to its balance of performance, ergonomics, and ecosystem alignment.

Middleware and Tower

Tower provides a foundational middleware abstraction used by Axum and other frameworks. The Service trait abstracts request-response handling, and middleware wraps services to add logging, authentication, rate limiting, or tracing. Tower’s layered architecture makes it easy to compose reusable middleware components across different services.

Rust in Production: Case Studies

Rust has been adopted in production by major technology companies for performance-critical and safety-critical systems. Mozilla uses Rust in the Firefox browser engine (Servo/Quantum) for improved memory safety and performance in CSS layout and rendering. Dropbox rewrote their sync engine core in Rust, achieving significant performance improvements and eliminating entire classes of bugs. Cloudflare uses Rust for edge computing infrastructure, including their Pingora HTTP proxy that handles 10% of the world’s internet traffic. Figma rewrote their multiplayer editing engine in Rust, achieving predictable performance and eliminating crashes. Discord used Rust for their read states service, handling millions of concurrent connections with minimal resources. These case studies demonstrate Rust’s value in systems where performance, reliability, and memory safety are critical. The common pattern: rewrite performance-critical components in Rust while maintaining the rest of the system in the original language, proving that incremental adoption works in practice.

Embedded Systems Programming

Rust is gaining traction in embedded systems for its memory safety guarantees without a garbage collector or runtime. The cortex-m-quickstart template provides a starting point for ARM Cortex-M microcontrollers. The RTIC framework provides real-time interrupt-driven concurrency. Embassy offers an async embedded runtime with USB, networking, and BLE support. Rust’s type system prevents common embedded errors like misconfiguring peripherals, incorrect register access, and data races between interrupt handlers and main code. The embedded ecosystem continues to mature with HAL implementations for major microcontroller families from STM, Nordic, and Espressif.

FAQ

What background knowledge is recommended? A basic understanding of programming fundamentals will help, but many concepts are explained from first principles.

How can I practice these skills? Start with small projects, contribute to open source, and build a portfolio. Consistent practice is more effective than occasional deep dives.

What tools do I need to get started? Most topics require only a text editor, the relevant runtime, and package manager. Specific tool recommendations are included throughout.

For a comprehensive overview, read our article on Getting Started With Rust.

For a comprehensive overview, read our article on Rust Async Programming.

Section: Rust 1459 words 7 min read Beginner 756 articles in section Report inaccuracy Back to top