axum

Axum (Rust) - Production Web APIs

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "axum" with this command: npx skills add bobmatnyc/claude-mpm-skills/bobmatnyc-claude-mpm-skills-axum

Axum (Rust) - Production Web APIs

Overview

Axum is a Rust web framework built on Hyper and Tower. Use it for type-safe request handling with composable middleware, structured errors, and excellent testability.

Quick Start

Minimal server

✅ Correct: typed handler + JSON response

use axum::{routing::get, Json, Router}; use serde::Serialize; use std::net::SocketAddr;

#[derive(Serialize)] struct Health { status: &'static str, }

async fn health() -> Json<Health> { Json(Health { status: "ok" }) }

#[tokio::main] async fn main() { let app = Router::new().route("/health", get(health));

let addr: SocketAddr = "0.0.0.0:3000".parse().unwrap();
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap();

}

❌ Wrong: block the async runtime

async fn handler() { std::thread::sleep(std::time::Duration::from_secs(1)); // blocks executor }

Core Concepts

Router + handlers

Handlers are async functions that return something implementing IntoResponse .

✅ Correct: route nesting

use axum::{routing::get, Router};

fn router() -> Router { let api = Router::new() .route("/users", get(list_users)) .route("/users/:id", get(get_user));

Router::new().nest("/api/v1", api)

}

async fn list_users() -> &'static str { "[]" } async fn get_user() -> &'static str { "{}" }

Extractors

Prefer extractors for parsing and validation at the boundary:

  • Path<T> : typed path params

  • Query<T> : query strings

  • Json<T> : JSON bodies

  • State<T> : shared application state

✅ Correct: typed path + JSON

use axum::{extract::Path, Json}; use serde::{Deserialize, Serialize};

#[derive(Deserialize)] struct CreateUser { email: String, }

#[derive(Serialize)] struct User { id: String, email: String, }

async fn create_user(Json(body): Json<CreateUser>) -> Json<User> { Json(User { id: "1".into(), email: body.email }) }

async fn get_user(Path(id): Path<String>) -> Json<User> { Json(User { id, email: "a@example.com".into() }) }

Production Patterns

  1. Shared state (DB pool, config, clients)

Use State<Arc<AppState>> and keep state immutable where possible.

✅ Correct: AppState via Arc

use axum::{extract::State, routing::get, Router}; use std::sync::Arc;

#[derive(Clone)] struct AppState { build_sha: &'static str, }

async fn version(State(state): State<Arc<AppState>>) -> String { state.build_sha.to_string() }

fn app(state: Arc<AppState>) -> Router { Router::new().route("/version", get(version)).with_state(state) }

  1. Structured error handling (IntoResponse )

Centralize error mapping to HTTP status codes and JSON.

✅ Correct: AppError converts into response

use axum::{http::StatusCode, response::IntoResponse, Json}; use serde::Serialize;

#[derive(Debug)] enum AppError { NotFound, BadRequest(&'static str), Internal, }

#[derive(Serialize)] struct ErrorBody { error: &'static str, }

impl IntoResponse for AppError { fn into_response(self) -> axum::response::Response { let (status, msg) = match self { AppError::NotFound => (StatusCode::NOT_FOUND, "not_found"), AppError::BadRequest(_) => (StatusCode::BAD_REQUEST, "bad_request"), AppError::Internal => (StatusCode::INTERNAL_SERVER_ERROR, "internal"), };

    (status, Json(ErrorBody { error: msg })).into_response()
}

}

  1. Middleware (Tower layers)

Use tower-http for production-grade layers: tracing, timeouts, request IDs, CORS.

✅ Correct: trace + timeout + CORS

use axum::{routing::get, Router}; use std::time::Duration; use tower::ServiceBuilder; use tower_http::{ cors::{Any, CorsLayer}, timeout::TimeoutLayer, trace::TraceLayer, };

fn app() -> Router { let layers = ServiceBuilder::new() .layer(TraceLayer::new_for_http()) .layer(TimeoutLayer::new(Duration::from_secs(10))) .layer(CorsLayer::new().allow_origin(Any));

Router::new()
    .route("/health", get(|| async { "ok" }))
    .layer(layers)

}

  1. Graceful shutdown

Terminate on SIGINT/SIGTERM and let in-flight requests drain.

✅ Correct: with_graceful_shutdown

async fn shutdown_signal() { let ctrl_c = async { tokio::signal::ctrl_c().await.ok(); };

#[cfg(unix)]
let terminate = async {
    tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
        .ok()
        .and_then(|mut s| s.recv().await);
};

#[cfg(not(unix))]
let terminate = std::future::pending::&#x3C;()>();

tokio::select! {
    _ = ctrl_c => {}
    _ = terminate => {}
}

}

#[tokio::main] async fn main() { let app = app(); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();

axum::serve(listener, app)
    .with_graceful_shutdown(shutdown_signal())
    .await
    .unwrap();

}

Testing

Test routers without sockets using tower::ServiceExt .

✅ Correct: request/response test

use axum::{body::Body, http::Request, Router}; use tower::ServiceExt;

#[tokio::test] async fn health_returns_ok() { let app: Router = super::app();

let res = app
    .oneshot(Request::builder().uri("/health").body(Body::empty()).unwrap())
    .await
    .unwrap();

assert_eq!(res.status(), 200);

}

Decision Trees

Axum vs other Rust frameworks

  • Prefer Axum for Tower middleware composition and typed extractors.

  • Prefer Actix Web for a mature ecosystem and actor-style runtime model.

  • Prefer Warp for functional filters and minimalism.

Anti-Patterns

  • Block the async runtime (std::thread::sleep , blocking I/O inside handlers).

  • Use unwrap() in request paths; return structured errors instead.

  • Run without timeouts; add request timeouts and upstream deadlines.

Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

drizzle-orm

No summary provided by upstream source.

Repository SourceNeeds Review
General

pydantic

No summary provided by upstream source.

Repository SourceNeeds Review
General

playwright-e2e-testing

No summary provided by upstream source.

Repository SourceNeeds Review
General

tailwind-css

No summary provided by upstream source.

Repository SourceNeeds Review