スキル一覧に戻る
imehr

rust-backend-guidelines

by imehr

0🍴 0📅 2026年1月15日
GitHubで見るManusで実行

SKILL.md


name: rust-backend-guidelines description: Axum/Actix-web backend patterns and best practices version: 1.0.0 triggers:

  • rust backend
  • axum
  • actix-web
  • actix
  • rocket
  • handler
  • extractor

Rust Backend Development Guidelines

Overview

This skill provides patterns and best practices for Rust backend development with Axum or Actix-web. Use this when creating APIs, handlers, or any server-side Rust logic.

Quick Reference

PatternWhen to UseExample
HandlerRequest handlingasync fn create_user()
ExtractorParse request dataJson<T>, Path<T>, Query<T>
StateShared application stateExtension<AppState>
MiddlewareCross-cutting concernstower::ServiceBuilder
ErrorError responsesimpl IntoResponse

Project Configuration

SettingDefaultYour Value
FrameworkAxumCHANGE_ME
RuntimeTokioCHANGE_ME
DatabaseSQLxCHANGE_ME
SerializationSerdeCHANGE_ME

Architecture Overview

Request Flow:
Router → Middleware → Handler → Service → Repository → Database
                        ↓
                   Extractors
                   (parse request)

Core Patterns

Pattern 1: Axum Router Structure

// ✅ CORRECT: Well-organized router
use axum::{
    routing::{get, post, put, delete},
    Router,
};

pub fn user_routes() -> Router<AppState> {
    Router::new()
        .route("/users", get(list_users).post(create_user))
        .route("/users/:id", get(get_user).put(update_user).delete(delete_user))
}

pub fn api_routes() -> Router<AppState> {
    Router::new()
        .nest("/api/v1", Router::new()
            .merge(user_routes())
            .merge(post_routes())
            .merge(auth_routes())
        )
}

// main.rs
#[tokio::main]
async fn main() {
    let state = AppState::new().await;

    let app = api_routes()
        .layer(TraceLayer::new_for_http())
        .layer(CorsLayer::permissive())
        .with_state(state);

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

Pattern 2: Handler Functions

// ✅ CORRECT: Clean handler with extractors
use axum::{
    extract::{Path, State, Json},
    http::StatusCode,
    response::IntoResponse,
};

/// Create a new user
pub async fn create_user(
    State(state): State<AppState>,
    Json(payload): Json<CreateUserRequest>,
) -> Result<impl IntoResponse, AppError> {
    let user = state.user_service.create(payload).await?;
    Ok((StatusCode::CREATED, Json(user)))
}

/// Get user by ID
pub async fn get_user(
    State(state): State<AppState>,
    Path(id): Path<i64>,
) -> Result<Json<UserResponse>, AppError> {
    let user = state.user_service
        .get_by_id(id)
        .await?
        .ok_or(AppError::NotFound("User not found"))?;
    Ok(Json(user))
}

/// List users with pagination
pub async fn list_users(
    State(state): State<AppState>,
    Query(params): Query<PaginationParams>,
) -> Result<Json<Vec<UserResponse>>, AppError> {
    let users = state.user_service.list(params.skip, params.limit).await?;
    Ok(Json(users))
}
// ❌ WRONG: Handler doing too much
pub async fn create_user(
    pool: Extension<PgPool>,
    Json(payload): Json<CreateUserRequest>,
) -> impl IntoResponse {
    // Don't put business logic in handlers
    if payload.email.is_empty() {
        return (StatusCode::BAD_REQUEST, "Email required").into_response();
    }
    let result = sqlx::query("INSERT INTO users ...")
        .execute(&*pool)
        .await;
    // ...
}

Pattern 3: Application State

// ✅ CORRECT: Structured application state
use std::sync::Arc;

#[derive(Clone)]
pub struct AppState {
    pub user_service: Arc<UserService>,
    pub post_service: Arc<PostService>,
    pub config: Arc<Config>,
}

impl AppState {
    pub async fn new() -> Self {
        let pool = PgPool::connect(&std::env::var("DATABASE_URL").unwrap())
            .await
            .expect("Failed to connect to database");

        let user_repo = Arc::new(UserRepository::new(pool.clone()));
        let post_repo = Arc::new(PostRepository::new(pool.clone()));

        Self {
            user_service: Arc::new(UserService::new(user_repo.clone())),
            post_service: Arc::new(PostService::new(post_repo, user_repo)),
            config: Arc::new(Config::from_env()),
        }
    }
}

Pattern 4: Request/Response Types

// ✅ CORRECT: Separate DTOs for requests and responses
use serde::{Deserialize, Serialize};
use validator::Validate;

#[derive(Debug, Deserialize, Validate)]
pub struct CreateUserRequest {
    #[validate(email)]
    pub email: String,
    #[validate(length(min = 1, max = 100))]
    pub name: String,
    #[validate(length(min = 8))]
    pub password: String,
}

#[derive(Debug, Deserialize)]
pub struct UpdateUserRequest {
    pub email: Option<String>,
    pub name: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct UserResponse {
    pub id: i64,
    pub email: String,
    pub name: String,
    pub created_at: DateTime<Utc>,
}

impl From<User> for UserResponse {
    fn from(user: User) -> Self {
        Self {
            id: user.id,
            email: user.email,
            name: user.name,
            created_at: user.created_at,
        }
    }
}

#[derive(Debug, Deserialize)]
pub struct PaginationParams {
    #[serde(default)]
    pub skip: i64,
    #[serde(default = "default_limit")]
    pub limit: i64,
}

fn default_limit() -> i64 { 20 }

Pattern 5: Middleware

// ✅ CORRECT: Middleware with tower
use axum::middleware::{self, Next};
use axum::extract::Request;
use axum::response::Response;

pub async fn auth_middleware(
    State(state): State<AppState>,
    mut request: Request,
    next: Next,
) -> Result<Response, AppError> {
    let token = request
        .headers()
        .get("Authorization")
        .and_then(|h| h.to_str().ok())
        .and_then(|h| h.strip_prefix("Bearer "))
        .ok_or(AppError::Unauthorized)?;

    let claims = state.auth_service.verify_token(token)?;
    request.extensions_mut().insert(claims);

    Ok(next.run(request).await)
}

// Apply middleware to routes
pub fn protected_routes() -> Router<AppState> {
    Router::new()
        .route("/me", get(get_current_user))
        .route("/settings", put(update_settings))
        .layer(middleware::from_fn_with_state(state.clone(), auth_middleware))
}

Pattern 6: Service Layer

// ✅ CORRECT: Service with business logic
pub struct UserService {
    repo: Arc<UserRepository>,
}

impl UserService {
    pub fn new(repo: Arc<UserRepository>) -> Self {
        Self { repo }
    }

    pub async fn create(&self, req: CreateUserRequest) -> Result<UserResponse, AppError> {
        // Validate
        req.validate().map_err(|e| AppError::Validation(e.to_string()))?;

        // Check for existing
        if self.repo.exists_by_email(&req.email).await? {
            return Err(AppError::Conflict("Email already registered"));
        }

        // Hash password
        let hashed = hash_password(&req.password)?;

        // Create user
        let user = self.repo.create(&req.email, &req.name, &hashed).await?;

        Ok(user.into())
    }

    pub async fn get_by_id(&self, id: i64) -> Result<Option<UserResponse>, AppError> {
        let user = self.repo.find_by_id(id).await?;
        Ok(user.map(Into::into))
    }
}

Anti-Patterns

Don't: Panic in Handlers

// ❌ BAD: Using unwrap/expect in handlers
pub async fn get_user(Path(id): Path<i64>) -> Json<User> {
    let user = repo.find_by_id(id).await.unwrap();  // Panic!
    Json(user.unwrap())  // Panic!
}

// ✅ GOOD: Return Result with proper error handling
pub async fn get_user(Path(id): Path<i64>) -> Result<Json<User>, AppError> {
    let user = repo.find_by_id(id).await?.ok_or(AppError::NotFound)?;
    Ok(Json(user))
}

Don't: Clone Heavy Data

// ❌ BAD: Cloning large data unnecessarily
pub async fn get_all(State(state): State<AppState>) -> Json<Vec<User>> {
    let users = state.cache.get_all().clone();  // Expensive clone!
    Json(users)
}

// ✅ GOOD: Use Arc for shared data
pub async fn get_all(State(state): State<AppState>) -> Json<Arc<Vec<User>>> {
    let users = state.cache.get_all();  // Arc clone is cheap
    Json(users)
}

Don't: Block the Runtime

// ❌ BAD: Blocking in async context
pub async fn process_file(body: Bytes) -> impl IntoResponse {
    std::fs::write("file.txt", &body).unwrap();  // Blocks runtime!
}

// ✅ GOOD: Use async file I/O or spawn_blocking
pub async fn process_file(body: Bytes) -> Result<(), AppError> {
    tokio::fs::write("file.txt", &body).await?;
    Ok(())
}

Resources

TopicLink
Handlers[mdc:resources/handlers.md]
Extractors[mdc:resources/extractors.md]
Middleware[mdc:resources/middleware.md]
State Management[mdc:resources/state.md]

スコア

総合スコア

50/100

リポジトリの品質指標に基づく評価

SKILL.md

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

+20
LICENSE

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

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

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

0/5
Issue管理

オープンIssueが50未満

+5
言語

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

+5
タグ

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

0/5

レビュー

💬

レビュー機能は近日公開予定です