Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
bobmatnyc avatar

Axum

  • 353 installs
  • 63 repo stars
  • Updated July 18, 2026
  • bobmatnyc/claude-mpm-skills

axum is a Claude Code skill that teaches production Rust HTTP API patterns with Axum routers, typed extractors, Tower middleware, shared state, and structured error handling for developers building async backend services

About

axum is a toolchain skill (version 1.1.1) from bobmatnyc/claude-mpm-skills documenting production Axum patterns on Hyper and Tower. It walks through Router composition, Path/Query/Json/State extractors, Arc-based AppState, IntoResponse error types, tracing and timeout layers, graceful shutdown, and tower::ServiceExt router tests. Progressive disclosure loads roughly 140 entry tokens or about 5500 full tokens. Reach for axum when scaffolding Rust microservices, internal APIs, or replacing blocking handlers in an async Tokio service. The parent repository bundles 171 Claude Code skills including 6 Rust toolchain skills covering Axum, Tauri, and Clap.

  • async routing
  • typed extractors
  • tower middleware
  • shared application state
  • structured error responses

Axum by the numbers

  • 353 all-time installs (skills.sh)
  • Ranked #1,188 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill axum

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs353
repo stars63
Last updatedJuly 18, 2026
Repositorybobmatnyc/claude-mpm-skills

How do you structure Axum routers for production Rust APIs?

Implement async Rust HTTP APIs with Axum routing, typed extractors, middleware, shared state, and structured error handling for production services.

Who is it for?

Rust backend developers shipping async HTTP APIs who want typed extractors, Tower middleware composition, and testable router patterns without reinventing production defaults.

Skip if: Developers building only frontend apps, Python/Node services, or Rust CLI tools that do not expose HTTP endpoints.

When should I use this skill?

The user is building or refactoring a Rust HTTP API with Axum, Tokio, or Tower and needs routing, middleware, state, error handling, or shutdown guidance.

What you get

Production-ready Axum Router layouts, typed handler patterns, middleware stacks, AppState definitions, structured error responses, tracing setup, and ServiceExt router tests.

  • Axum Router handler modules
  • Tower middleware configuration
  • Structured error types implementing IntoResponse

By the numbers

  • Skill version 1.1.1 with progressive disclosure at ~140 entry tokens and ~5500 full tokens
  • Parent claude-mpm-skills repository bundles 171 skills including 6 Rust toolchain skills

Files

SKILL.mdMarkdownGitHub ↗

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() })
}

Dependencies & Crate Structure

If your crate is published as a library in addition to being built as a binary, isolate the HTTP stack to avoid bloating library consumers.

Correct: optional HTTP feature

[dependencies]
axum = { version = "0.7", optional = true }
tower-http = { version = "0.5", optional = true }
tokio = { version = "1", features = ["full"] }

[features]
default = ["http-server"]
http-server = ["axum", "tower-http"]

[[bin]]
name = "my-service"
required-features = ["http-server"]

This way:

  • Library consumers (cargo add my-lib) get just the core logic without the HTTP overhead.
  • Binary builds include the server by default: cargo install my-crate works as expected.
  • Opt-out is explicit: cargo add my-crate --no-default-features for library use.

Wrong: unconditional HTTP dependencies

# Never do this if the crate is also a library:
axum = "0.7"
tower-http = "0.5"
# Library users now pull in the entire web stack

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)
}

2) 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()
    }
}

3) 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)
}

4) 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::<()>();

    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();
}
Ops: Graceful Shutdown in Supervised Environments

Signal handling above is application-level. In production, your supervisor (systemd, launchd, container orchestrator) controls the actual termination window.

systemd (Linux): Set KillSignal=SIGTERM and TimeoutStopSec=120 (or higher) in your .service file.

  • The default 90s timeout can fire mid-fsync on networked storage (EBS/EFS), truncating writes.
  • 120s gives in-flight HTTP requests time to drain plus a safety margin for filesystem syncs.

launchd (macOS): Use launchctl bootout (sends SIGTERM and waits) instead of launchctl kickstart -k (SIGKILL, truncates in-flight I/O).

Client-side reconnection: Have HTTP clients and MCP bridges connecting to the service implement exponential backoff (starting 200ms, capped at 30s) so brief restarts are transparent and don't cascade errors upstream.

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

  • Axum docs: https://docs.rs/axum
  • Tower HTTP layers: https://docs.rs/tower-http
  • Tracing: https://docs.rs/tracing

Related skills

How it compares

Pick axum over generic API-design skills when the stack is Rust and you need Axum-specific extractor, Tower layer, and ServiceExt testing idioms.

FAQ

What Rust crates does the axum skill assume?

The axum skill assumes axum on Hyper and Tower with Tokio as the async runtime and commonly tower-http for tracing, timeouts, and CORS layers. Its quick start lists adding axum, tokio, and tower-http before defining Router handlers and AppState.

When should I pick Axum over Actix Web?

The axum skill recommends Axum when developers need Tower middleware composition and typed extractors at request boundaries. It points to Actix Web when prioritizing a mature ecosystem or actor-style runtime patterns instead of Tower layers.

Does the axum skill include testing patterns?

Yes. The axum skill documents testing routers with tower::ServiceExt so handlers can be exercised without binding a live TCP port. That pattern supports fast unit tests for status codes, JSON bodies, and middleware ordering.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.