Back to list
caioniehues

rust-backend-axum

by caioniehues

My dotfiles - Claude Code configuration

0🍴 0📅 Dec 23, 2025

SKILL.md


name: rust-backend-axum description: Build production-ready Rust backend APIs with Axum framework. Covers router composition, state management, extractors, middleware (auth, tracing), error handling with IntoResponse, and graceful shutdown. Use when building Rust web services, REST APIs, or when working with Axum, Tower middleware, or async Rust backends.

<essential_principles> 1. Extractors Order Matters

Body-consuming extractors (Json, Form, Bytes) must come LAST in handler parameters.

// CORRECT: State and Path before Json
async fn handler(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
    Json(body): Json<CreateRequest>,
) -> impl IntoResponse { }

2. State Must Be Clone

AppState passed to .with_state() must implement Clone. Use Arc for expensive-to-clone fields.

#[derive(Clone)]
struct AppState {
    db: sqlx::PgPool,          // PgPool is cheap to clone
    config: Arc<Config>,        // Arc for expensive data
}

3. route_layer vs layer

  • route_layer() - Applies only to routes defined BEFORE it
  • layer() - Applies to ALL routes </essential_principles>

<quick_start> Minimal Axum Application

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

#[derive(Clone)]
struct AppState {
    db: sqlx::PgPool,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let db = sqlx::PgPool::connect(&std::env::var("DATABASE_URL")?).await?;
    let state = AppState { db };

    let app = Router::new()
        .route("/health", get(|| async { "ok" }))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

</quick_start>

pub fn create_router(state: AppState) -> Router {
    Router::new()
        .nest("/api/v1", api_routes())
        .route("/health", axum::routing::get(health))
        .with_state(state)
}

fn api_routes() -> Router<AppState> {
    Router::new()
        .nest("/users", users::routes())
        .nest("/items", items::routes())
}
use axum::{
    extract::{Request, Extension},
    middleware::Next,
    http::{header, StatusCode},
};

pub async fn auth_middleware(
    mut req: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let auth_header = req.headers()
        .get(header::AUTHORIZATION)
        .and_then(|h| h.to_str().ok())
        .and_then(|h| h.strip_prefix("Bearer "));

    let token = auth_header.ok_or(StatusCode::UNAUTHORIZED)?;
    let user = validate_jwt(token).await.map_err(|_| StatusCode::UNAUTHORIZED)?;

    req.extensions_mut().insert(user);
    Ok(next.run(req).await)
}

// Apply to routes
pub fn protected_routes() -> Router<AppState> {
    Router::new()
        .route("/profile", get(protected_handler))
        .route_layer(middleware::from_fn(auth_middleware))
}
use tower::ServiceBuilder;
use tower_http::{trace::TraceLayer, timeout::TimeoutLayer, compression::CompressionLayer};

let app = Router::new()
    .nest("/api", api_routes())
    .with_state(state)
    .layer(
        ServiceBuilder::new()
            .layer(TraceLayer::new_for_http())
            .layer(TimeoutLayer::new(Duration::from_secs(30)))
            .layer(CompressionLayer::new())
    );

<success_criteria>

  • Router compiles with all routes registered
  • State is Clone and contains Arc for expensive fields
  • Extractors are ordered correctly (body consumers last)
  • Error type implements IntoResponse
  • Auth middleware uses Extension for user propagation
  • Graceful shutdown handles SIGTERM/SIGINT
  • cargo clippy passes without warnings </success_criteria>

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon