Skip to main content
skill CX Codex

axum — Claude Code skill: Rust API patterns

A skill with Axum patterns for Rust services: typed handlers, Tower middleware, structured errors, tracing and graceful shutdown.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add bobmatnyc/claude-mpm-skills --skill axum -a claude-code
Open on GitHub

What axum does

The skill walks through building an Axum service: routers with nested routes, extractors like Path, Query, Json and State, and shared state held in Arc<AppState>. A separate section maps a domain error to an HTTP status and JSON body through IntoResponse.

Middleware comes as tower-http layers: tracing, timeouts, CORS. Shutdown handling covers SIGINT and SIGTERM so in-flight requests can finish. Router tests use ServiceExt.

Examples pin axum 0.7, tower-http 0.5 and tokio with the full feature, so check those against your own Cargo.toml before pasting code. When a crate ships as both a library and a binary, the skill recommends an optional feature for the HTTP stack, otherwise everyone who depends on the library pulls in the web dependencies.

What it helps with

  • set up a typed router with nested routes
  • turn a domain error into a status code and JSON body
  • chain tracing, timeout and CORS layers
  • drain in-flight requests on shutdown signals
  • test a router without binding a real port

Example prompts for the agent

  • Build an axum router with a nested /api/v1 and typed handlers
  • Add an AppError type with IntoResponse and status mapping
  • Wire graceful shutdown on SIGTERM for my axum server

How to install axum

1Skills CLI (Vercel)

npx skills add bobmatnyc/claude-mpm-skills --skill axum -a claude-code

2Manually: put the files in place

~/.claude/skills/axum/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/axum/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about axum

How do I install axum in Claude Code?

Run npx skills add bobmatnyc/claude-mpm-skills --skill axum -a claude-code. The skill lands at ~/.claude/skills/axum/SKILL.md.

What does the axum skill do?

It provides Axum patterns for production APIs: routers, extractors, state, middleware, error handling, tracing, graceful shutdown and testing.

Does it require specific crate versions?

The examples use axum 0.7, tower-http 0.5 and tokio with the full feature. Compare them with your project before copying.

Source text

SKILL.md · MIT
---
name: axum
description: "Axum (Rust) web framework patterns for production APIs: routers/extractors, state, middleware, error handling, tracing, graceful shutdown, and testing"
user-invocable: false
disable-model-invocation: true
version: 1.1.1
category: toolchain
author: Claude MPM Team
license: MIT
progressive_disclosure:
  entry_point:
    summary: "Build production Rust APIs with Axum using typed extractors, Tower middleware, structured errors, tracing, and testable routers"
    when_to_use: "When building Rust HTTP APIs/services that need predictable middleware composition, strong typing, and production-ready shutdown/observability patterns"
    quick_start: "1. Add axum + tokio + tower-http 2. Build Router with typed handlers 3. Define AppState + error type 4. Add tracing + timeouts 5. Test router with ServiceExt"
  token_estimate:
    entry: 140
    full: 5500
context_limit: 800
tags:
  - rust
  - axum
  - tokio
  - http
  - api
  - tower
  - middleware
  - tracing
requires_tools: []
---

# 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**
```rust
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**
```rust
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**
```rust
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**
```rust
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: "[email protected]".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**
```toml
[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**
```toml
# 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**
```rust
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**
```rust
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**
```rust
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**
```rust
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**
```rust
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

© Claude MPM Team · MIT · bobmatnyc/claude-mpm-skills

Similar skills

All skills for Backend and APIs →

From the same set73

wordpress-security-validation A skill for safe WordPress plugin and theme development: sanitize input, validate logic, escape output. Useful when handling forms, AJAX, or rendering untrusted content. CX Codex +1 581installs fastify A skill that feeds your coding agent production Fastify patterns for TypeScript: schema validation, plugins, error handling, inject tests. CX Codex +1 571installs git-workflow A skill that hands your agent ready Git patterns: commits, branches, conflicts, rebase and hooks. Useful whenever version control is in play. CX Codex +1 556installs zustand A skill that teaches your agent zustand: a small hook-based state manager for React with no providers. Handy when you need global state but not Redux. CX Codex +1 485installs hono-core A skill covering Hono routing, context, handlers and response patterns, so the same code runs on Cloudflare Workers, Deno, Bun and Node.js. CX Codex +1 483installs asyncio A Python asyncio skill covering coroutines, tasks, the event loop, aiohttp and async FastAPI endpoints. Useful when code waits on network or database. CX Codex +1 476installs typescript-core A skill with modern TypeScript patterns: a strict tsconfig, runtime validation, advanced types. Useful once a TS codebase outgrows its type setup. CX Codex +1 475installs web-performance-optimization A skill that walks through web performance work: Core Web Vitals, modern browser patterns, and framework-specific fixes. Useful when Lighthouse scores drop or pages load slowly. CX Codex +1 457installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor