Web Backends Basics
11 examples to get you started with Rust web backends - 8 basic and 3 intermediate.
Search across all documentation pages
11 examples to get you started with Rust web backends - 8 basic and 3 intermediate.
# Cargo.toml
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tower-http = { version = "0.6", features = ["trace"] }cargo run after adding a #[tokio::main] entry point.Minimal Axum app listening on port 3000.
use axum::{routing::get, Router};
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(|| async { "Hello, Axum" }));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}Router::new() composes routes into a single service.axum::serve runs the app on a Tokio listener.IntoResponse.Related: Axum Routing & Handlers - routes and methods
Return typed JSON from a handler.
use axum::{routing::get, Json, Router};
use serde::Serialize;
#[derive(Serialize)]
struct Health { status: &'static str }
async fn health() -> Json<Health> {
Json(Health { status: "ok" })
}
let app = Router::new().route("/health", get(health));Json<T> sets Content-Type: application/json.Serialize on response types.Related: Extractors & State -
Jsonextractor
Capture typed values from the URL.
use axum::extract::Path;
async fn get_user(Path(id): Path<u64>) -> String {
format!("user {id}")
}Path extractor parses and validates route segments./:id.Optional filters from the query string.
use axum::extract::Query;
use serde::Deserialize;
#[derive(Deserialize)]
struct ListParams { limit: Option<u32>, q: Option<String> }
async fn list(Query(params): Query<ListParams>) -> String {
format!("limit={:?} q={:?}", params.limit, params.q)
}Query deserializes into any Deserialize type.None for Option.serde(default) for sensible defaults.Accept a typed request body.
use axum::{routing::post, Json};
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateItem { name: String }
async fn create(Json(body): Json<CreateItem>) -> String {
format!("created {}", body.name)
}Json extractor deserializes the request body.Related: Request Validation - field constraints
Inject a database pool or config via State.
use axum::extract::State;
use std::sync::Arc;
#[derive(Clone)]
struct AppState { db_url: Arc<String> }
async fn info(State(state): State<AppState>) -> String {
state.db_url.clone()
}State<T> requires T: Clone - wrap heavy data in Arc..with_state(state) on the router.Related: Extractors & State - state patterns
Group routes under a prefix.
use axum::{routing::get, Router};
let users = Router::new()
.route("/", get(|| async { "list users" }))
.route("/:id", get(|| async { "get user" }));
let app = Router::new().nest("/users", users);nest mounts a sub-router at a path prefix.Router::merge for sibling groups.Different handlers per HTTP method on one path.
use axum::routing::{get, post};
let app = Router::new().route("/items", get(list_items).post(create_item));.get(), .post(), .delete() on a route.Related: Axum Routing & Handlers - method routing
Add request logging middleware.
use tower_http::trace::TraceLayer;
let app = Router::new()
.route("/health", get(|| async { "ok" }))
.layer(TraceLayer::new_for_http());tower-http layers wrap the entire router.Related: Middleware & Tower - layer composition
Return consistent error JSON.
use axum::{http::StatusCode, response::IntoResponse, Json};
use serde::Serialize;
#[derive(Serialize)]
struct ApiError { code: &'static str, message: String }
impl IntoResponse for ApiError {
fn into_response(self) -> axum::response::Response {
(StatusCode::BAD_REQUEST, Json(self)).into_response()
}
}IntoResponse.Related: Error Handling in Handlers - error enums
Stop accepting new connections on SIGTERM.
use tokio::signal;
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app)
.with_graceful_shutdown(async {
signal::ctrl_c().await.ok();
})
.await
.unwrap();with_graceful_shutdown drains in-flight requests.Related: Testing Web Services - integration tests
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Reviewed by Chris St. John·Last updated Jul 16, 2026