#Building Servers
Rust's async ecosystem makes it well-suited for building high-performance web servers. This chapter covers building HTTP APIs with axum, the most popular async web framework.
#Hello World Server
use axum::{routing::get, Router};
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(|| async { "Hello, World!" }));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
#Route Handlers
Handlers are async functions that extract data from requests and return responses:
use axum::{extract::Path, http::StatusCode, Json};
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
struct User {
id: u64,
name: String,
}
async fn get_user(Path(id): Path<u64>) -> Json<User> {
Json(User {
id,
name: "Alice".to_string(),
})
}
#[derive(Deserialize)]
struct CreateUserRequest {
name: String,
email: String,
}
async fn create_user(
Json(payload): Json<CreateUserRequest>,
) -> (StatusCode, Json<User>) {
let user = User {
id: 1,
name: payload.name,
};
(StatusCode::CREATED, Json(user))
}
#Application State
Share state across handlers using State:
use axum::{extract::State, routing::get, Router};
use std::sync::Arc;
use tokio::sync::RwLock;
struct AppState {
counter: RwLock<u64>,
db_pool: sqlx::PgPool,
}
async fn increment(State(state): State<Arc<AppState>>) -> String {
let mut counter = state.counter.write().await;
*counter += 1;
format!("Count: {counter}")
}
#[tokio::main]
async fn main() {
let state = Arc::new(AppState {
counter: RwLock::new(0),
db_pool: create_pool().await,
});
let app = Router::new()
.route("/increment", get(increment))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
#Middleware
Add cross-cutting concerns with middleware layers:
use axum::{middleware, Router};
use std::time::Instant;
async fn timing_middleware(
request: axum::extract::Request,
next: middleware::Next,
) -> axum::response::Response {
let start = Instant::now();
let response = next.run(request).await;
let duration = start.elapsed();
println!("Request took {duration:?}");
response
}
let app = Router::new()
.route("/", get(handler))
.layer(middleware::from_fn(timing_middleware));
#Error Handling
Return proper error responses:
use axum::{http::StatusCode, response::IntoResponse, Json};
use serde_json::json;
enum AppError {
NotFound(String),
Internal(String),
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
let (status, message) = match self {
AppError::NotFound(resource) => {
(StatusCode::NOT_FOUND, format!("{resource} not found"))
}
AppError::Internal(message) => {
(StatusCode::INTERNAL_SERVER_ERROR, message)
}
};
let body = Json(json!({ "error": message }));
(status, body).into_response()
}
}
async fn get_user(Path(id): Path<u64>) -> Result<Json<User>, AppError> {
find_user(id)
.await
.map(Json)
.ok_or_else(|| AppError::NotFound(format!("User {id}")))
}
#Summary
Axum provides a type-safe, composable approach to building web servers. Its extractor system makes request parsing explicit and its tower middleware integration gives access to a rich ecosystem of reusable middleware layers.