Token导航 LogoToken导航

Agent Skill · 开发

axum-web-framework(axum 网络框架)

axum-web-framework 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

axum-web-framework 是开发类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证为 MIT,第三方扫描有提醒项,按安装量在开发类的 21,746 个 Skill 里排第 8,228。

总安装 3,270周安装 131GitHub Stars 53下载 1,058核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:axum-web-framework(axum 网络框架)
来源仓库:https://github.com/manutej/luxor-claude-marketplace
仓库路径:skills/axum-web-framework
安装命令:
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill axum-web-framework
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill axum-web-framework

这个 Skill 是做什么的

开发类 Skill 面向写代码、改代码和排查问题,一般会读写仓库文件,部分还会执行构建或测试命令。

axum-web-framework 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。

全面的 Axum 网络框架技能,涵盖路由、提取器、中间件和状态管理等生产级应用开发。通过 npx skills add 命令从指定 GitHub 仓库安装,需结合 Tokio 生态系统使用。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写操作。

适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

来源仓库内路径:skills/axum-web-framework

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/manutej/luxor-claude-marketplace --skill axum-…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

axum-web-framework 怎么安装?

在终端执行 npx skills add https://github.com/manutej/luxor-claude-marketplace --skill axum-web-framew…。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

axum-web-framework 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

axum-web-framework 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

axum-web-framework 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

axum-web-framework 用的人多吗?

公开数据是累计安装 3,270 次,近一周安装 131 次,下载 1,058 次,GitHub 53 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

axum-web-framework 安全吗?

第三方扫描有提醒项,说明存在需要人工确认的行为。安装前请打开源码看清它读了什么、联网到哪里。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。

先想清楚的地方

  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理开发类任务、又不想每次都手写提示词的人。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

开发类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
skill-vetter

技能审查员 · OpenClaw

8,779,018986操作浏览器MIT-0通过
ontology

本体论 · OpenClaw

6,636,960576只读MIT-0通过
entra-app-registration

内部应用程序注册 · Codex、Claude

6,631,872764接触敏感数据未明确通过
entra-app-registration

内部应用程序注册 · Codex、Claude

3,757,680201接触敏感数据未明确通过
nano-banana-pro

纳米香蕉专业版 · OpenClaw

3,526,898364接触敏感数据MIT-0有提醒
axum-web-framework

axum 网络框架 · 本页

3,27053需要联网MIT有提醒

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Axum Web Framework

A comprehensive skill for building production-ready web applications and APIs using Axum, the ergonomic and modular Rust web framework built on Tokio and Tower. Master routing, extractors, middleware, state management, error handling, and deployment patterns.

When to Use This Skill

Use this skill when:

  • Building REST APIs with Rust and async/await
  • Creating high-performance web services with type safety
  • Developing microservices with Tokio ecosystem integration
  • Implementing WebSocket servers or Server-Sent Events (SSE)
  • Building GraphQL APIs with Rust backend
  • Creating middleware-heavy applications requiring Tower integration
  • Developing production-ready web applications requiring robust error handling
  • Building systems requiring fine-grained control over HTTP request/response handling
  • Implementing authentication, authorization, and security middleware
  • Creating real-time web applications with async Rust
  • Developing APIs requiring request validation and transformation
  • Building web services with complex routing and nested routers
  • Implementing rate limiting, timeout, and backpressure handling
  • Creating web applications requiring custom extractors and response types

Core Concepts

Axum Architecture Philosophy

Axum is built on three fundamental pillars:

  1. Tower Services: Everything in Axum is built on Tower's Service trait, providing composability and middleware integration
  2. Type-Safe Extractors: Request data extraction is compile-time checked, eliminating runtime parsing errors
  3. Minimal Boilerplate: Ergonomic APIs that reduce ceremony while maintaining explicitness

The Router

The Router is the central building block in Axum. It maps HTTP requests to handlers based on path and method.

Key Properties:

  • Routes are matched in the order they're defined
  • Routers can be nested for modular organization
  • Middleware can be applied at router, route, or method level
  • Generic over state type for flexible state management
  • Implements Tower's Service trait for composability

Router Creation:

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

let app = Router::new()
    .route("/", get(handler))
    .route("/users/:id", get(get_user))
    .route("/posts", get(list_posts).post(create_post));

Handlers

Handlers are async functions that process requests and return responses. Axum supports multiple handler signatures through its powerful type system.

Handler Requirements:

  • Must be async functions
  • Can extract data from requests using extractors
  • Must return types implementing IntoResponse
  • Can have up to 16 parameters (all must be extractors)

Common Handler Patterns:

// Simple handler
async fn handler() -> &'static str {
    "Hello, World!"
}

// Handler with path parameter
async fn get_user(Path(user_id): Path<u32>) -> String {
    format!("User ID: {}", user_id)
}

// Handler with multiple extractors
async fn create_user(
    State(state): State<AppState>,
    Json(payload): Json<CreateUser>,
) -> Result<Json<User>, StatusCode> {
    // Implementation
}

Extractors

Extractors are types that implement FromRequest or FromRequestParts, allowing type-safe extraction of data from requests.

Built-in Extractors:

  1. Path - Extract path parameters
  2. Query - Extract query string parameters
  3. Json - Parse JSON request body
  4. Form - Parse form-encoded request body
  5. State - Access shared application state
  6. Extension - Access request extensions
  7. Headers - Access request headers
  8. Method - Get HTTP method
  9. Uri - Get request URI
  10. Request - Get full request
  11. Bytes - Raw request body as bytes
  12. String - Request body as UTF-8 string
  13. Multipart - Handle multipart/form-data

Extractor Ordering:

  • Extractors that consume the request body must come last
  • Multiple body extractors in one handler will cause compilation errors
  • State and other non-body extractors can be in any order

Responses

Any type implementing IntoResponse can be returned from handlers. Axum provides many built-in implementations.

Built-in Response Types:

  • String, &'static str - Text responses
  • Json<T> - JSON responses
  • Html<String> - HTML responses
  • StatusCode - Status-only responses
  • (StatusCode, T) - Status with body
  • (Parts, T) - Custom headers with body
  • Response - Full control over response
  • Result<T, E> - Error handling (where E: IntoResponse)

State Management

Axum uses the State extractor to share data across handlers. State must implement Clone and is typically wrapped in Arc for shared ownership.

State Patterns:

  1. Simple State:
#[derive(Clone)]
struct AppState {
    api_key: String,
}

let app = Router::new()
    .route("/", get(handler))
    .with_state(AppState {
        api_key: "secret".to_string(),
    });
  1. Shared State with Arc:
#[derive(Clone)]
struct AppState {
    db_pool: Arc<DatabasePool>,
    cache: Arc<RwLock<Cache>>,
}
  1. Multiple State Types:
// Define separate state types for different router sections
let api_router: Router<ApiState> = Router::new()
    .route("/api/data", get(api_handler));

let app_router: Router<AppState> = Router::new()
    .route("/app", get(app_handler));

// Combine with final state
let app = Router::new()
    .nest("/", app_router.with_state(app_state))
    .nest("/", api_router.with_state(api_state));

Middleware

Middleware in Axum comes from Tower and provides request/response transformation, logging, authentication, and more.

Middleware Categories:

  1. Tower Middleware - From tower and tower-http crates
  2. Custom Middleware - Using middleware::from_fn
  3. Service Middleware - Implementing Tower's Service trait
  4. Layer Pattern - Using Tower's Layer for composability

Middleware Application Order:

  • Applied with .layer() executes bottom-to-top (wrapping previous layers)
  • Applied with ServiceBuilder executes top-to-bottom (more intuitive)
  • Middleware on Router::layer runs after routing
  • Middleware around Router (using Layer::layer) runs before routing

Error Handling

Axum's error handling is built on the IntoResponse trait, allowing custom error types to be converted to HTTP responses.

Error Handling Strategies:

  1. Result Types:
async fn handler() -> Result<Json<Data>, StatusCode> {
    // Returns 200 OK or error status code
}
  1. Custom Error Types:
enum AppError {
    Database(sqlx::Error),
    NotFound,
    Unauthorized,
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        // Convert to HTTP response
    }
}
  1. HandleErrorLayer:
use axum::error_handling::HandleErrorLayer;

let app = Router::new()
    .layer(
        ServiceBuilder::new()
            .layer(HandleErrorLayer::new(handle_error))
            .layer(TimeoutLayer::new(Duration::from_secs(30)))
    );

Tower Integration

Axum is built on Tower, enabling powerful middleware composition and service abstraction.

Key Tower Concepts:

  1. Service Trait - Asynchronous request processing
  2. Layer Trait - Middleware factory pattern
  3. ServiceBuilder - Ergonomic middleware composition
  4. Timeout - Request timeout handling
  5. RateLimit - Request rate limiting
  6. LoadShed - Backpressure management
  7. Buffer - Request buffering

Routing

Basic Routing

Routes map HTTP methods and paths to handlers:

use axum::{
    Router,
    routing::{get, post, put, delete, patch},
};

let app = Router::new()
    .route("/", get(root))
    .route("/users", get(list_users).post(create_user))
    .route("/users/:id", get(get_user).put(update_user).delete(delete_user))
    .route("/posts/:id/comments", get(get_comments).post(add_comment));

Path Parameters

Extract dynamic segments from paths:

use axum::extract::Path;
use serde::Deserialize;

// Single parameter
async fn get_user(Path(user_id): Path<u32>) -> String {
    format!("User {}", user_id)
}

// Multiple parameters
#[derive(Deserialize)]
struct PostPath {
    user_id: u32,
    post_id: u32,
}

async fn get_post(Path(params): Path<PostPath>) -> String {
    format!("User {} Post {}", params.user_id, params.post_id)
}

// Using tuple for multiple params
async fn get_comment(
    Path((post_id, comment_id)): Path<(u32, u32)>
) -> String {
    format!("Post {} Comment {}", post_id, comment_id)
}

Wildcard Routes

Capture remaining path segments:

// Captures all remaining path
async fn handler(Path(path): Path<String>) -> String {
    format!("Captured path: {}", path)
}

let app = Router::new()
    .route("/{*key}", get(handler));

// GET /foo/bar/baz -> path = "foo/bar/baz"

Important: Nested routers strip matched prefixes, but wildcard routes retain the full URI.

Nested Routers

Organize routes into modules using router nesting:

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

fn api_routes() -> Router {
    Router::new()
        .route("/users", get(list_users))
        .route("/users/:id", get(get_user))
        .route("/posts", get(list_posts))
}

fn admin_routes() -> Router {
    Router::new()
        .route("/dashboard", get(dashboard))
        .route("/settings", get(settings))
}

let app = Router::new()
    .nest("/api", api_routes())
    .nest("/admin", admin_routes())
    .route("/", get(root));

Nesting Behavior:

  • Matched prefix is stripped from URI before passing to nested router
  • Handlers in nested routers only see path relative to nest point
  • Fallback handlers are inherited from parent if not defined in child
  • Middleware can be applied before or after nesting

Fallback Handlers

Handle unmatched routes:

use axum::{http::StatusCode, handler::Handler};

async fn fallback() -> (StatusCode, &'static str) {
    (StatusCode::NOT_FOUND, "Not Found")
}

let app = Router::new()
    .route("/", get(handler))
    .fallback(fallback);

Fallback Inheritance:

async fn api_fallback() -> (StatusCode, &'static str) {
    (StatusCode::NOT_FOUND, "API endpoint not found")
}

let api = Router::new()
    .route("/users", get(list_users))
    .fallback(api_fallback);

let app = Router::new()
    .nest("/api", api)
    .fallback(fallback); // Used for non-/api routes

Method Routing

Handle multiple HTTP methods on the same route:

use axum::routing::{get, post, MethodRouter};

// Multiple methods on one route
let app = Router::new()
    .route("/users", get(list_users).post(create_user));

// Different handlers per method
let app = Router::new()
    .route("/resource",
        get(get_resource)
            .post(create_resource)
            .put(update_resource)
            .delete(delete_resource)
            .patch(patch_resource)
    );

Extractors Deep Dive

Path Extractor

Extract typed path parameters:

use axum::extract::Path;
use serde::Deserialize;

// Simple extraction
async fn user_by_id(Path(id): Path<u32>) -> String {
    format!("User {}", id)
}

// Complex extraction
#[derive(Deserialize)]
struct Params {
    org: String,
    repo: String,
    issue: u32,
}

async fn github_issue(Path(params): Path<Params>) -> String {
    format!("{}/{} issue #{}", params.org, params.repo, params.issue)
}

let app = Router::new()
    .route("/users/:id", get(user_by_id))
    .route("/repos/:org/:repo/issues/:issue", get(github_issue));

Query Extractor

Extract query string parameters:

use axum::extract::Query;
use serde::Deserialize;

#[derive(Deserialize)]
struct Pagination {
    page: Option<u32>,
    per_page: Option<u32>,
}

async fn list_users(Query(pagination): Query<Pagination>) -> String {
    let page = pagination.page.unwrap_or(1);
    let per_page = pagination.per_page.unwrap_or(20);
    format!("Page {} with {} items", page, per_page)
}

// GET /users?page=2&per_page=50

Json Extractor

Parse JSON request bodies:

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

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

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

async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
    let user = User {
        id: 123,
        username: payload.username,
        email: payload.email,
    };
    Json(user)
}

JSON Error Handling:

use axum::extract::rejection::JsonRejection;
use axum::http::StatusCode;

async fn create_user(
    payload: Result<Json<CreateUser>, JsonRejection>
) -> Result<Json<User>, (StatusCode, String)> {
    match payload {
        Ok(Json(create_user)) => {
            // Valid JSON
            Ok(Json(User { /* ... */ }))
        }
        Err(JsonRejection::MissingJsonContentType(_)) => {
            Err((
                StatusCode::BAD_REQUEST,
                "Missing `Content-Type: application/json`".to_string(),
            ))
        }
        Err(JsonRejection::JsonDataError(err)) => {
            Err((
                StatusCode::BAD_REQUEST,
                format!("Invalid JSON: {}", err),
            ))
        }
        Err(JsonRejection::JsonSyntaxError(err)) => {
            Err((
                StatusCode::BAD_REQUEST,
                format!("JSON syntax error: {}", err),
            ))
        }
        Err(_) => {
            Err((
                StatusCode::INTERNAL_SERVER_ERROR,
                "Unknown error".to_string(),
            ))
        }
    }
}

State Extractor

Access shared application state:

use axum::extract::State;
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    db_pool: Arc<DatabasePool>,
    api_key: String,
}

async fn handler(State(state): State<AppState>) -> String {
    format!("API Key: {}", state.api_key)
}

let state = AppState {
    db_pool: Arc::new(DatabasePool::new()),
    api_key: "secret".to_string(),
};

let app = Router::new()
    .route("/", get(handler))
    .with_state(state);

Extension Extractor

Access request extensions (useful for middleware):

use axum::extract::Extension;

#[derive(Clone)]
struct CurrentUser {
    id: u32,
    username: String,
}

async fn handler(Extension(user): Extension<CurrentUser>) -> String {
    format!("Hello, {}", user.username)
}

// Set by middleware:
async fn auth_middleware(
    mut req: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let user = CurrentUser {
        id: 1,
        username: "alice".to_string(),
    };
    req.extensions_mut().insert(user);
    Ok(next.run(req).await)
}

Form Extractor

Parse form-encoded request bodies:

use axum::extract::Form;
use serde::Deserialize;

#[derive(Deserialize)]
struct LoginForm {
    username: String,
    password: String,
}

async fn login(Form(form): Form<LoginForm>) -> String {
    format!("Logging in user: {}", form.username)
}

Headers Extractor

Access request headers:

use axum::http::HeaderMap;

async fn handler(headers: HeaderMap) -> String {
    let user_agent = headers
        .get("user-agent")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("unknown");
    format!("User-Agent: {}", user_agent)
}

Custom Extractors

Create custom extractors by implementing FromRequest or FromRequestParts:

use axum::{
    extract::{FromRequest, Request},
    response::{IntoResponse, Response},
    http::StatusCode,
    async_trait,
};

struct AuthenticatedUser {
    id: u32,
    username: String,
}

#[async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
    S: Send + Sync,
{
    type Rejection = Response;

    async fn from_request_parts(
        parts: &mut Parts,
        state: &S,
    ) -> Result<Self, Self::Rejection> {
        // Extract and validate auth token
        let auth_header = parts
            .headers
            .get("authorization")
            .and_then(|v| v.to_str().ok())
            .ok_or_else(|| {
                (StatusCode::UNAUTHORIZED, "Missing authorization header")
                    .into_response()
            })?;

        // Validate token and return user
        Ok(AuthenticatedUser {
            id: 1,
            username: "alice".to_string(),
        })
    }
}

async fn protected_route(user: AuthenticatedUser) -> String {
    format!("Hello, {}", user.username)
}

Middleware

Applying Middleware

Three Ways to Apply Middleware:

  1. Router-level - Affects all routes
  2. Route-level - Affects specific routes
  3. Method-level - Affects specific methods on a route
use axum::{Router, routing::get, middleware};
use tower_http::trace::TraceLayer;

// 1. Router-level
let app = Router::new()
    .route("/", get(handler))
    .layer(TraceLayer::new_for_http());

// 2. Route-level
let app = Router::new()
    .route("/protected", get(handler))
    .route_layer(middleware::from_fn(auth_middleware));

// 3. Method-level
let app = Router::new()
    .route("/resource",
        get(handler)
            .route_layer(middleware::from_fn(read_only_auth))
            .post(create_handler)
            .route_layer(middleware::from_fn(write_auth))
    );

Middleware Execution Order

With sequential .layer() calls (bottom-to-top):

let app = Router::new()
    .route("/", get(handler))
    .layer(layer_three)  // Executes third
    .layer(layer_two)    // Executes second
    .layer(layer_one);   // Executes first

With ServiceBuilder (top-to-bottom):

use tower::ServiceBuilder;

let app = Router::new()
    .route("/", get(handler))
    .layer(
        ServiceBuilder::new()
            .layer(layer_one)    // Executes first
            .layer(layer_two)    // Executes second
            .layer(layer_three)  // Executes third
    );

Common Tower Middleware

TraceLayer - HTTP request tracing:

use tower_http::trace::TraceLayer;

let app = Router::new()
    .route("/", get(handler))
    .layer(TraceLayer::new_for_http());

CompressionLayer - Response compression:

use tower_http::compression::CompressionLayer;

let app = Router::new()
    .route("/", get(handler))
    .layer(CompressionLayer::new());

CorsLayer - CORS handling:

use tower_http::cors::{CorsLayer, Any};

let cors = CorsLayer::new()
    .allow_origin(Any)
    .allow_methods(Any)
    .allow_headers(Any);

let app = Router::new()
    .route("/api/data", get(handler))
    .layer(cors);

TimeoutLayer - Request timeouts:

use tower::timeout::TimeoutLayer;
use std::time::Duration;

let app = Router::new()
    .route("/", get(handler))
    .layer(TimeoutLayer::new(Duration::from_secs(30)));

HandleErrorLayer

Convert middleware errors to HTTP responses:

use axum::{
    error_handling::HandleErrorLayer,
    http::{StatusCode, Method, Uri},
    BoxError,
};
use tower::ServiceBuilder;
use std::time::Duration;

async fn handle_timeout_error(
    method: Method,
    uri: Uri,
    err: BoxError,
) -> (StatusCode, String) {
    if err.is::<tower::timeout::error::Elapsed>() {
        (
            StatusCode::REQUEST_TIMEOUT,
            format!("`{} {}` request timed out", method, uri),
        )
    } else {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("`{} {}` failed with {}", method, uri, err),
        )
    }
}

let app = Router::new()
    .route("/", get(handler))
    .layer(
        ServiceBuilder::new()
            .layer(HandleErrorLayer::new(handle_timeout_error))
            .layer(TimeoutLayer::new(Duration::from_secs(30)))
    );

Custom Middleware with from_fn

Create custom middleware using async functions:

use axum::{
    middleware::{self, Next},
    extract::Request,
    response::Response,
    http::StatusCode,
};

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

    if let Some(auth_header) = auth_header {
        if validate_token(auth_header).await {
            Ok(next.run(req).await)
        } else {
            Err(StatusCode::UNAUTHORIZED)
        }
    } else {
        Err(StatusCode::UNAUTHORIZED)
    }
}

let app = Router::new()
    .route("/protected", get(handler))
    .layer(middleware::from_fn(auth_middleware));

Passing data from middleware to handler:

use axum::extract::Extension;

#[derive(Clone)]
struct CurrentUser {
    id: u32,
    username: String,
}

async fn auth_middleware(
    mut req: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let auth_header = req.headers()
        .get("authorization")
        .and_then(|h| h.to_str().ok())
        .ok_or(StatusCode::UNAUTHORIZED)?;

    if let Some(user) = authorize_user(auth_header).await {
        req.extensions_mut().insert(user);
        Ok(next.run(req).await)
    } else {
        Err(StatusCode::UNAUTHORIZED)
    }
}

async fn handler(Extension(user): Extension<CurrentUser>) -> String {
    format!("Hello, {}", user.username)
}

let app = Router::new()
    .route("/", get(handler))
    .layer(middleware::from_fn(auth_middleware));

Custom Tower Middleware

Implement Tower's Service and Layer traits for full control:

use tower::{Service, Layer};
use axum::{response::Response, extract::Request};
use std::task::{Context, Poll};
use futures_core::future::BoxFuture;

#[derive(Clone)]
struct MyLayer;

impl<S> Layer<S> for MyLayer {
    type Service = MyMiddleware<S>;

    fn layer(&self, inner: S) -> Self::Service {
        MyMiddleware { inner }
    }
}

#[derive(Clone)]
struct MyMiddleware<S> {
    inner: S,
}

impl<S> Service<Request> for MyMiddleware<S>
where
    S: Service<Request, Response = Response> + Send + 'static,
    S::Future: Send + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, request: Request) -> Self::Future {
        // Process request
        let future = self.inner.call(request);
        Box::pin(async move {
            let response: Response = future.await?;
            // Process response
            Ok(response)
        })
    }
}

let app = Router::new()
    .route("/", get(handler))
    .layer(MyLayer);

Middleware with State Access

Create middleware that accesses application state:

use axum::extract::State;
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    db: Arc<Database>,
}

#[derive(Clone)]
struct MyLayer {
    state: AppState,
}

impl<S> Layer<S> for MyLayer {
    type Service = MyService<S>;

    fn layer(&self, inner: S) -> Self::Service {
        MyService {
            inner,
            state: self.state.clone(),
        }
    }
}

#[derive(Clone)]
struct MyService<S> {
    inner: S,
    state: AppState,
}

impl<S> Service<Request> for MyService<S>
where
    S: Service<Request>,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = S::Future;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: Request) -> Self::Future {
        // Use self.state here
        self.inner.call(req)
    }
}

let state = AppState {
    db: Arc::new(Database::new()),
};

let app = Router::new()
    .route("/", get(handler))
    .layer(MyLayer { state: state.clone() })
    .with_state(state);

Middleware Before Routing

Apply middleware before routing (e.g., for URI rewriting):

use tower::Layer;
use axum::ServiceExt;

fn rewrite_request_uri(req: Request) -> Request {
    // Modify request URI
    req
}

let middleware = tower::util::MapRequestLayer::new(rewrite_request_uri);

let app = Router::new()
    .route("/", get(handler));

// Apply layer around entire router
let app_with_middleware = middleware.layer(app);

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

State Management

Basic State

Simple state with cloneable types:

use axum::extract::State;

#[derive(Clone)]
struct AppState {
    config: Config,
    api_key: String,
}

async fn handler(State(state): State<AppState>) -> String {
    format!("Config: {:?}, Key: {}", state.config, state.api_key)
}

let state = AppState {
    config: Config::default(),
    api_key: "secret".to_string(),
};

let app = Router::new()
    .route("/", get(handler))
    .with_state(state);

Shared State with Arc

Use Arc for shared ownership of expensive-to-clone types:

use std::sync::Arc;
use tokio::sync::RwLock;

#[derive(Clone)]
struct AppState {
    db_pool: Arc<DatabasePool>,
    cache: Arc<RwLock<HashMap<String, String>>>,
    config: Config, // Cheap to clone
}

async fn handler(State(state): State<AppState>) -> Result<String, StatusCode> {
    // Access database pool
    let conn = state.db_pool.get().await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    // Access cache (read)
    let cache = state.cache.read().await;
    let value = cache.get("key");

    // Access cache (write)
    drop(cache); // Release read lock
    let mut cache = state.cache.write().await;
    cache.insert("new_key".to_string(), "value".to_string());

    Ok("Success".to_string())
}

Multiple State Types

Use different state types for different router sections:

#[derive(Clone)]
struct ApiState {
    api_key: String,
}

#[derive(Clone)]
struct AppState {
    db: Arc<Database>,
}

fn api_routes() -> Router<ApiState> {
    Router::new()
        .route("/data", get(|State(state): State<ApiState>| async move {
            format!("API Key: {}", state.api_key)
        }))
}

fn app_routes() -> Router<AppState> {
    Router::new()
        .route("/users", get(|State(state): State<AppState>| async move {
            "Users".to_string()
        }))
}

let api_state = ApiState { api_key: "secret".to_string() };
let app_state = AppState { db: Arc::new(Database::new()) };

let app = Router::new()
    .nest("/api", api_routes().with_state(api_state))
    .nest("/app", app_routes().with_state(app_state));

Generic State in Functions

Return routers with generic state for flexibility:

fn routes<S>() -> Router<S>
where
    S: Clone + Send + Sync + 'static,
{
    Router::new()
        .route("/health", get(|| async { "OK" }))
        .route("/version", get(|| async { "1.0.0" }))
}

// Can be combined with any state type
let app = Router::new()
    .merge(routes())
    .route("/", get(handler))
    .with_state(AppState { /* ... */ });

State Transitions

Chain routers with different state requirements:

#[derive(Clone)]
struct StateA {
    data_a: String,
}

#[derive(Clone)]
struct StateB {
    data_b: String,
}

let router_a: Router<StateA> = Router::new()
    .route("/a", get(|State(s): State<StateA>| async move { s.data_a }));

// Provide StateA, next missing state is StateB
let router_b: Router<StateB> = router_a.with_state(StateA {
    data_a: "A".to_string(),
});

// Add routes needing StateB
let router_b = router_b
    .route("/b", get(|State(s): State<StateB>| async move { s.data_b }));

// Provide StateB, now we have Router<()>
let app: Router<()> = router_b.with_state(StateB {
    data_b: "B".to_string(),
});

Error Handling

Basic Error Handling with Result

Use Result to handle errors in handlers:

use axum::http::StatusCode;

async fn handler() -> Result<String, StatusCode> {
    let result = some_operation().await;
    match result {
        Ok(data) => Ok(format!("Success: {}", data)),
        Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
    }
}

Custom Error Types

Implement IntoResponse for custom errors:

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

#[derive(Debug)]
enum AppError {
    Database(sqlx::Error),
    NotFound,
    Unauthorized,
    ValidationError(String),
}

#[derive(Serialize)]
struct ErrorResponse {
    error: String,
    message: String,
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, error_message) = match self {
            AppError::Database(e) => {
                tracing::error!("Database error: {}", e);
                (StatusCode::INTERNAL_SERVER_ERROR, "Database error")
            }
            AppError::NotFound => (StatusCode::NOT_FOUND, "Resource not found"),
            AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"),
            AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, &msg),
        };

        let body = Json(ErrorResponse {
            error: status.to_string(),
            message: error_message.to_string(),
        });

        (status, body).into_response()
    }
}

// Use in handler
async fn get_user(Path(id): Path<u32>) -> Result<Json<User>, AppError> {
    let user = db.get_user(id).await.map_err(AppError::Database)?;
    user.ok_or(AppError::NotFound).map(Json)
}

Extractor Rejection Handling

Handle extractor rejections for better error messages:

use axum::extract::rejection::JsonRejection;

async fn create_user(
    payload: Result<Json<CreateUser>, JsonRejection>,
) -> Result<Json<User>, AppError> {
    let Json(create_user) = payload.map_err(|err| match err {
        JsonRejection::MissingJsonContentType(_) => {
            AppError::ValidationError("Content-Type must be application/json".to_string())
        }
        JsonRejection::JsonDataError(e) => {
            AppError::ValidationError(format!("Invalid JSON: {}", e))
        }
        JsonRejection::JsonSyntaxError(e) => {
            AppError::ValidationError(format!("JSON syntax error: {}", e))
        }
        _ => AppError::ValidationError("Invalid request body".to_string()),
    })?;

    // Process create_user
    Ok(Json(user))
}

Custom Extractors with Error Handling

Create extractors with custom rejection types:

use axum::{
    extract::{FromRequest, Request},
    response::{IntoResponse, Response},
    async_trait,
};

struct ValidatedJson<T>(T);

#[async_trait]
impl<S, T> FromRequest<S> for ValidatedJson<T>
where
    T: serde::de::DeserializeOwned + Validate,
    S: Send + Sync,
{
    type Rejection = Response;

    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
        let Json(data) = Json::<T>::from_request(req, state)
            .await
            .map_err(|err| {
                (
                    StatusCode::BAD_REQUEST,
                    format!("Invalid JSON: {}", err),
                ).into_response()
            })?;

        data.validate().map_err(|err| {
            (
                StatusCode::BAD_REQUEST,
                format!("Validation error: {}", err),
            ).into_response()
        })?;

        Ok(ValidatedJson(data))
    }
}

async fn handler(ValidatedJson(data): ValidatedJson<MyData>) -> String {
    // data is validated
    format!("Received: {:?}", data)
}

Middleware Error Handling

Handle errors from fallible middleware:

use axum::error_handling::HandleErrorLayer;
use tower::ServiceBuilder;

async fn handle_timeout_error(err: BoxError) -> (StatusCode, String) {
    if err.is::<tower::timeout::error::Elapsed>() {
        (
            StatusCode::REQUEST_TIMEOUT,
            "Request took too long".to_string(),
        )
    } else {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("Unhandled error: {}", err),
        )
    }
}

let app = Router::new()
    .route("/", get(handler))
    .layer(
        ServiceBuilder::new()
            .layer(HandleErrorLayer::new(handle_timeout_error))
            .layer(TimeoutLayer::new(Duration::from_secs(30)))
    );

Fallible Services

Route to services that can fail:

use axum::error_handling::HandleError;

async fn fallible_operation() -> Result<(), anyhow::Error> {
    // Operation that might fail
    Ok(())
}

let fallible_service = tower::service_fn(|_req| async {
    fallible_operation().await?;
    Ok::<_, anyhow::Error>(Response::new(Body::empty()))
});

async fn handle_error(err: anyhow::Error) -> (StatusCode, String) {
    (
        StatusCode::INTERNAL_SERVER_ERROR,
        format!("Something went wrong: {}", err),
    )
}

let app = Router::new().route_service(
    "/",
    HandleError::new(fallible_service, handle_error),
);

Response Building

JSON Responses

Return JSON with type safety:

use axum::Json;
use serde::Serialize;

#[derive(Serialize)]
struct ApiResponse {
    success: bool,
    data: String,
}

async fn handler() -> Json<ApiResponse> {
    Json(ApiResponse {
        success: true,
        data: "Hello".to_string(),
    })
}

HTML Responses

Serve HTML content:

use axum::response::Html;

async fn handler() -> Html<&'static str> {
    Html("<h1>Hello, World!</h1>")
}

async fn dynamic_html(Path(name): Path<String>) -> Html<String> {
    Html(format!("<h1>Hello, {}</h1>", name))
}

Status Code Responses

Return different status codes:

use axum::http::StatusCode;

async fn handler() -> StatusCode {
    StatusCode::NO_CONTENT
}

async fn with_body() -> (StatusCode, String) {
    (StatusCode::CREATED, "Resource created".to_string())
}

async fn json_with_status() -> (StatusCode, Json<ApiResponse>) {
    (StatusCode::CREATED, Json(ApiResponse { /* ... */ }))
}

Custom Headers

Add custom headers to responses:

use axum::http::{HeaderMap, header};

async fn handler() -> (HeaderMap, String) {
    let mut headers = HeaderMap::new();
    headers.insert(header::CACHE_CONTROL, "max-age=3600".parse().unwrap());
    headers.insert("X-Custom-Header", "value".parse().unwrap());
    (headers, "Response body".to_string())
}

Full Response Control

Build complete responses:

use axum::{
    response::Response,
    http::{StatusCode, header},
    body::Body,
};

async fn handler() -> Response {
    Response::builder()
        .status(StatusCode::OK)
        .header(header::CONTENT_TYPE, "application/json")
        .header("X-Custom", "value")
        .body(Body::from(r#"{"status":"ok"}"#))
        .unwrap()
}

Streaming Responses

Stream data to clients:

use axum::response::sse::{Event, Sse};
use futures::stream::{self, Stream};
use std::convert::Infallible;

async fn sse_handler() -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
    let stream = stream::iter(0..10).map(|i| {
        Ok(Event::default().data(format!("Event {}", i)))
    });
    Sse::new(stream)
}

File Downloads

Serve files for download:

use axum::{
    response::{Response, IntoResponse},
    http::{header, StatusCode},
    body::Body,
};
use tokio::fs::File;

async fn download_file() -> Result<Response, StatusCode> {
    let file = File::open("path/to/file.pdf")
        .await
        .map_err(|_| StatusCode::NOT_FOUND)?;

    let body = Body::from_stream(tokio_util::io::ReaderStream::new(file));

    Ok(Response::builder()
        .status(StatusCode::OK)
        .header(header::CONTENT_TYPE, "application/pdf")
        .header(
            header::CONTENT_DISPOSITION,
            "attachment; filename=\"file.pdf\"",
        )
        .body(body)
        .unwrap())
}

Production Patterns

Database Integration

Integrate with database pools:

use sqlx::{PgPool, postgres::PgPoolOptions};
use std::sync::Arc;

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

async fn get_user(
    State(state): State<AppState>,
    Path(id): Path<i64>,
) -> Result<Json<User>, AppError> {
    let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
        .fetch_optional(&state.db)
        .await
        .map_err(AppError::Database)?
        .ok_or(AppError::NotFound)?;

    Ok(Json(user))
}

#[tokio::main]
async fn main() {
    let db = PgPoolOptions::new()
        .max_connections(5)
        .connect(&env::var("DATABASE_URL").unwrap())
        .await
        .unwrap();

    let state = AppState { db };

    let app = Router::new()
        .route("/users/:id", get(get_user))
        .with_state(state);

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

Configuration Management

Use environment variables and configuration:

use serde::Deserialize;
use config::{Config, ConfigError, Environment};

#[derive(Debug, Deserialize, Clone)]
struct Settings {
    database_url: String,
    redis_url: String,
    jwt_secret: String,
    port: u16,
}

impl Settings {
    fn new() -> Result<Self, ConfigError> {
        Config::builder()
            .add_source(Environment::default())
            .build()?
            .try_deserialize()
    }
}

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

#[tokio::main]
async fn main() {
    let settings = Settings::new().expect("Failed to load configuration");

    let db = PgPoolOptions::new()
        .connect(&settings.database_url)
        .await
        .expect("Failed to connect to database");

    let state = AppState {
        settings: settings.clone(),
        db,
    };

    let app = Router::new()
        .route("/", get(handler))
        .with_state(state);

    let addr = format!("0.0.0.0:{}", settings.port);
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Structured Logging

Implement comprehensive logging:

use tracing::{info, error, debug, instrument};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[tokio::main]
async fn main() {
    tracing_subscriber::registry()
        .with(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "info".into()),
        )
        .with(tracing_subscriber::fmt::layer())
        .init();

    info!("Starting server");

    let app = Router::new()
        .route("/", get(handler))
        .layer(TraceLayer::new_for_http());

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    info!("Listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

#[instrument]
async fn handler(Path(id): Path<u32>) -> Result<String, AppError> {
    debug!("Handling request for user {}", id);

    match get_user_from_db(id).await {
        Ok(user) => {
            info!("Successfully retrieved user {}", id);
            Ok(format!("User: {}", user))
        }
        Err(e) => {
            error!("Failed to get user {}: {}", id, e);
            Err(AppError::Database(e))
        }
    }
}

Graceful Shutdown

Implement graceful shutdown handling:

use tokio::signal;

async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

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

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

    println!("Signal received, starting graceful shutdown");
}

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

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

Health Checks

Implement health check endpoints:

use serde::Serialize;

#[derive(Serialize)]
struct HealthResponse {
    status: String,
    database: String,
    cache: String,
}

async fn health_check(State(state): State<AppState>) -> Json<HealthResponse> {
    let db_status = match sqlx::query("SELECT 1").fetch_one(&state.db).await {
        Ok(_) => "healthy",
        Err(_) => "unhealthy",
    };

    let cache_status = match state.redis.ping().await {
        Ok(_) => "healthy",
        Err(_) => "unhealthy",
    };

    Json(HealthResponse {
        status: if db_status == "healthy" && cache_status == "healthy" {
            "healthy".to_string()
        } else {
            "degraded".to_string()
        },
        database: db_status.to_string(),
        cache: cache_status.to_string(),
    })
}

let app = Router::new()
    .route("/health", get(health_check))
    .route("/ready", get(readiness_check))
    .with_state(state);

Rate Limiting

Implement rate limiting:

use tower::limit::RateLimitLayer;
use std::time::Duration;

let app = Router::new()
    .route("/api/data", get(handler))
    .layer(RateLimitLayer::new(
        100, // max requests
        Duration::from_secs(60), // per minute
    ));

Request Validation

Validate requests with custom extractors:

use validator::{Validate, ValidationError};

#[derive(Debug, Deserialize, Validate)]
struct CreateUserRequest {
    #[validate(length(min = 3, max = 50))]
    username: String,
    #[validate(email)]
    email: String,
    #[validate(length(min = 8))]
    password: String,
}

struct ValidatedJson<T>(T);

#[async_trait]
impl<S, T> FromRequest<S> for ValidatedJson<T>
where
    T: DeserializeOwned + Validate,
    S: Send + Sync,
{
    type Rejection = (StatusCode, String);

    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
        let Json(data) = Json::<T>::from_request(req, state)
            .await
            .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;

        data.validate()
            .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;

        Ok(ValidatedJson(data))
    }
}

async fn create_user(
    ValidatedJson(user): ValidatedJson<CreateUserRequest>,
) -> Result<Json<User>, AppError> {
    // user is validated
    Ok(Json(User { /* ... */ }))
}

Testing

Unit Testing Handlers

Test handlers in isolation:

#[cfg(test)]
mod tests {
    use super::*;
    use axum::body::Body;
    use axum::http::{Request, StatusCode};
    use tower::ServiceExt;

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

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

        assert_eq!(response.status(), StatusCode::OK);
    }
}

Integration Testing

Test full application:

#[cfg(test)]
mod tests {
    use super::*;
    use axum::body::Body;
    use axum::http::{Request, StatusCode, Method};
    use tower::ServiceExt;
    use serde_json::json;

    #[tokio::test]
    async fn test_create_user() {
        let state = AppState::test();
        let app = create_app(state);

        let request_body = json!({
            "username": "testuser",
            "email": "test@example.com"
        });

        let response = app
            .oneshot(
                Request::builder()
                    .method(Method::POST)
                    .uri("/users")
                    .header("content-type", "application/json")
                    .body(Body::from(request_body.to_string()))
                    .unwrap()
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::CREATED);
    }
}

Best Practices

Handler Organization

  1. Keep handlers thin - Move business logic to service layer
  2. Use extractors - Let type system handle extraction
  3. Return Result types - Use custom error types
  4. Instrument handlers - Add tracing for observability

State Management

  1. Use Arc for expensive types - Database pools, caches
  2. Keep state minimal - Only what's truly shared
  3. Implement Clone - Required for State extractor
  4. Avoid mutation - Use interior mutability (RwLock, Mutex) when needed

Error Handling

  1. Create custom error types - Implement IntoResponse
  2. Provide context - Include helpful error messages
  3. Log errors appropriately - Use tracing
  4. Return proper status codes - Match HTTP semantics

Middleware

  1. Use ServiceBuilder - More intuitive ordering
  2. Apply at right level - Router vs route vs method
  3. Handle errors - Use HandleErrorLayer for fallible middleware
  4. Keep middleware focused - Single responsibility

Performance

  1. Use connection pooling - For databases and external services
  2. Enable compression - CompressionLayer for responses
  3. Implement caching - Reduce redundant operations
  4. Use backpressure - LoadShedLayer, RateLimitLayer
  5. Optimize serialization - Use efficient JSON libraries

Security

  1. Validate inputs - Use custom extractors with validation
  2. Implement authentication - Use middleware for auth
  3. Use HTTPS - In production environments
  4. Set security headers - CORS, CSP, etc.
  5. Rate limit - Prevent abuse

Common Patterns

Repository Pattern

trait UserRepository {
    async fn get(&self, id: u32) -> Result<User, AppError>;
    async fn create(&self, user: CreateUser) -> Result<User, AppError>;
    async fn update(&self, id: u32, user: UpdateUser) -> Result<User, AppError>;
    async fn delete(&self, id: u32) -> Result<(), AppError>;
}

struct PostgresUserRepository {
    pool: PgPool,
}

impl UserRepository for PostgresUserRepository {
    async fn get(&self, id: u32) -> Result<User, AppError> {
        // Implementation
    }
}

#[derive(Clone)]
struct AppState {
    user_repo: Arc<dyn UserRepository + Send + Sync>,
}

async fn get_user(
    State(state): State<AppState>,
    Path(id): Path<u32>,
) -> Result<Json<User>, AppError> {
    let user = state.user_repo.get(id).await?;
    Ok(Json(user))
}

Service Layer Pattern

struct UserService {
    repo: Arc<dyn UserRepository + Send + Sync>,
    email_service: Arc<EmailService>,
}

impl UserService {
    async fn create_user(&self, data: CreateUser) -> Result<User, AppError> {
        let user = self.repo.create(data).await?;
        self.email_service.send_welcome_email(&user).await?;
        Ok(user)
    }
}

#[derive(Clone)]
struct AppState {
    user_service: Arc<UserService>,
}

Versioned APIs

fn v1_routes() -> Router<AppState> {
    Router::new()
        .route("/users", get(v1::list_users))
        .route("/users/:id", get(v1::get_user))
}

fn v2_routes() -> Router<AppState> {
    Router::new()
        .route("/users", get(v2::list_users))
        .route("/users/:id", get(v2::get_user))
}

let app = Router::new()
    .nest("/api/v1", v1_routes())
    .nest("/api/v2", v2_routes())
    .with_state(state);

Skill Version: 1.0.0 Last Updated: October 2025 Skill Category: Web Development, REST APIs, Rust, Async Programming Compatible With: Axum 0.7+, Tokio 1.0+, Tower 0.4+

平台分布

Claude Code

26.48%
按下载量换算280

OpenCode

22.24%
按下载量换算235

Codex

17.98%
按下载量换算190

Gemini CLI

10.69%
按下载量换算113

trae

7.31%
按下载量换算77

Antigravity

3.55%
按下载量换算38

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。

继续浏览同类 Skills