smithery/imehr

rust-backend-guidelines

Axum/Actix-web backend patterns and best practices

Installation

$ npx skills add smithery/imehr --skill rust-backend-guidelines

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/imehr · top by installs.

npx skills add smithery/imehr

Browse all from smithery/imehr

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Version1.0.0

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 9,191 B
  • docs SUMMARY.md 81 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

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

Pattern When to Use Example
Handler Request handling async fn create_user()
Extractor Parse request data Json<T>, Path<T>, Query<T>
State Shared application state Extension<AppState>
Middleware Cross-cutting concerns tower::ServiceBuilder
Error Error responses impl IntoResponse

Project Configuration

<!-- CUSTOMIZE START -->

Setting Default Your Value
Framework Axum CHANGE_ME
Runtime Tokio CHANGE_ME
Database SQLx CHANGE_ME
Serialization Serde CHANGE_ME

<!-- CUSTOMIZE END -->

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

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