Rust Web Development: Actix, Rocket, and Axum
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
| Feature | Axum | Actix-web | Rocket |
|---|---|---|---|
| Async runtime | Tokio | Tokio | Tokio (since 0.5) |
| Routing | Extractor-based | Macro + builder | Attribute macros |
| Middleware | Tower ecosystem | Built-in + Tower | Built-in + fairings |
| Database | Any via Tower | Any via Actors | Any via state |
| Maturity | High (used by major orgs) | Very high | High |
| Ergonomics | Excellent | Good | Excellent |
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
thiserrorfor error types — Return meaningful error types from handlers - Add observability — Use
tracingfor structured logging andtower-httptrace middleware - Validate input — Use
validatororgardefor request validation - Database connection pooling — Reuse connections with
sqlx::PgPoolordeadpool - Rate limiting — Use
tower-governororactix-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.