Claude-skill-registry backend-rust
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/backend-rust" ~/.claude/skills/majiayu000-claude-skill-registry-backend-rust && rm -rf "$T"
skills/data/backend-rust/SKILL.mdRust Backend Stack
🚨 FIRST: Project Protection Setup
MANDATORY before writing any code. Run this on every new project:
# 1. Create .gitignore (ALWAYS include these) cat >> .gitignore << 'EOF' # Build artifacts target/ Cargo.lock # IDE .idea/ .vscode/ .DS_Store # Secrets .env .env.* !.env.example *.key *.pem credentials.json EOF # 2. Check pre-commit installed which pre-commit || pip install pre-commit # 3. Setup pre-commit config cat > .pre-commit-config.yaml << 'EOF' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-added-large-files args: ['--maxkb=500'] - id: detect-private-key - repo: https://github.com/gitleaks/gitleaks rev: v8.21.2 hooks: - id: gitleaks EOF # 4. Install hooks pre-commit install # 5. Verify git status # Should show .gitignore and .pre-commit-config.yaml
Why mandatory: Prevents accidental commit of
target/ (2GB+), secrets, credentials.
For comprehensive secret protection, use:
skill: secrets-guardian
Quick Reference
| Topic | Reference |
|---|---|
| Testing | testing.md — axum-test, mockall, async tests |
| CI/CD | ci-cd.md — GitHub Actions, Docker, caching |
| Cross-Compile | cross-compile.md — targets, musl, static binaries |
Automation Scripts
| Script | Purpose | Usage |
|---|---|---|
| Build debug/release | |
| Run tests + coverage | |
| Format + clippy | |
| Security audit | |
| Full CI check | |
Scripts auto-detect
Cargo.toml. Use --path to specify location.
Tooling
| Tool | Purpose | Why |
|---|---|---|
| Axum | Web framework | Tower ecosystem, ergonomic |
| SQLx | Database | Compile-time checked queries |
| tokio | Async runtime | Industry standard |
| serde | Serialization | JSON, TOML, etc. |
| tracing | Logging | Structured, async-aware |
| Shuttle | Deploy | Free tier, simple |
Dependencies
CRITICAL: Always use Context7 to check latest crate versions before adding dependencies:
mcp__context7__resolve-library-id → mcp__context7__get-library-docs
Versions in this skill are examples — verify current versions via Context7 or crates.io.
Project Setup
Rust version: 1.85+ required (edition 2024 support). Current stable: 1.91.0.
NOTE: Rust edition 2024 is stable since Rust 1.85 (February 2025). Always use
edition = "2024" for new projects.
cargo new my-api cd my-api
# Cargo.toml [package] name = "my-api" version = "0.1.0" edition = "2024" # Stable since Rust 1.85 (Feb 2025) [dependencies] axum = "0.8" tokio = { version = "1", features = ["full"] } sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "uuid", "chrono"] } serde = { version = "1", features = ["derive"] } serde_json = "1" tower-http = { version = "0.5", features = ["cors", "trace"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } dotenvy = "0.15" thiserror = "1" uuid = { version = "1", features = ["v4", "serde"] } chrono = { version = "0.4", features = ["serde"] } [dev-dependencies] axum-test = "15"
Project Structure
src/ ├── main.rs # Entry point ├── config.rs # Environment config ├── db.rs # Database pool ├── error.rs # Error types ├── routes/ │ ├── mod.rs │ ├── health.rs │ └── users.rs ├── models/ │ ├── mod.rs │ └── user.rs ├── handlers/ │ └── users.rs └── middleware/ └── auth.rs migrations/ └── 001_create_users.sql tests/ └── api_tests.rs
Axum Patterns
Basic App
use axum::{routing::get, Router}; use std::net::SocketAddr; use tower_http::trace::TraceLayer; #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); let app = Router::new() .route("/health", get(|| async { "OK" })) .layer(TraceLayer::new_for_http()); let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); axum::serve(listener, app).await.unwrap(); }
App State
use axum::extract::FromRef; use sqlx::PgPool; #[derive(Clone, FromRef)] pub struct AppState { pub db: PgPool, pub config: Config, } // In main.rs let state = AppState { db: pool, config }; let app = Router::new() .route("/users", get(list_users).post(create_user)) .with_state(state);
Handler with Extractors
use axum::{ extract::{Path, State, Json}, http::StatusCode, response::IntoResponse, }; use sqlx::PgPool; pub async fn get_user( State(db): State<PgPool>, Path(id): Path<i32>, ) -> Result<Json<User>, AppError> { let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id) .fetch_optional(&db) .await? .ok_or(AppError::NotFound)?; Ok(Json(user)) } pub async fn create_user( State(db): State<PgPool>, Json(input): Json<CreateUser>, ) -> Result<impl IntoResponse, AppError> { let user = sqlx::query_as!( User, "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *", input.email, input.name ) .fetch_one(&db) .await?; Ok((StatusCode::CREATED, Json(user))) }
Request/Response Types
use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; use uuid::Uuid; #[derive(Serialize, sqlx::FromRow)] pub struct User { pub id: i32, pub public_id: Uuid, pub email: String, pub name: String, pub created_at: DateTime<Utc>, } #[derive(Deserialize)] pub struct CreateUser { pub email: String, pub name: String, } #[derive(Serialize)] pub struct UserList { pub data: Vec<User>, pub total: i64, }
Error Handling
use axum::{ http::StatusCode, response::{IntoResponse, Response}, Json, }; use serde_json::json; use thiserror::Error; #[derive(Error, Debug)] pub enum AppError { #[error("Not found")] NotFound, #[error("Validation error: {0}")] Validation(String), #[error("Database error")] Database(#[from] sqlx::Error), #[error("Internal error")] Internal(#[from] anyhow::Error), } impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, message) = match &self { AppError::NotFound => (StatusCode::NOT_FOUND, "Not found"), AppError::Validation(msg) => (StatusCode::BAD_REQUEST, msg.as_str()), AppError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Database error"), AppError::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal error"), }; let body = Json(json!({ "error": { "message": message } })); (status, body).into_response() } }
SQLx Patterns
Database Pool
use sqlx::postgres::PgPoolOptions; pub async fn create_pool(database_url: &str) -> sqlx::PgPool { PgPoolOptions::new() .max_connections(5) .connect(database_url) .await .expect("Failed to create pool") }
Migrations
-- migrations/001_create_users.sql CREATE TABLE users ( id SERIAL PRIMARY KEY, public_id UUID DEFAULT gen_random_uuid() NOT NULL, email VARCHAR(255) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL ); CREATE INDEX idx_users_email ON users(email);
# Run migrations sqlx migrate run # Create new migration sqlx migrate add create_posts
Compile-time Checked Queries
// Requires DATABASE_URL in .env for compile-time checking let users = sqlx::query_as!( User, r#" SELECT id, public_id, email, name, created_at FROM users WHERE email LIKE $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3 "#, format!("%{}%", search), limit, offset ) .fetch_all(&pool) .await?;
Transactions
let mut tx = pool.begin().await?; sqlx::query!("INSERT INTO users (email, name) VALUES ($1, $2)", email, name) .execute(&mut *tx) .await?; sqlx::query!("INSERT INTO profiles (user_id, bio) VALUES ($1, $2)", user_id, bio) .execute(&mut *tx) .await?; tx.commit().await?;
Config
use serde::Deserialize; #[derive(Clone, Deserialize)] pub struct Config { pub database_url: String, pub port: u16, pub jwt_secret: String, } impl Config { pub fn from_env() -> Self { dotenvy::dotenv().ok(); envy::from_env().expect("Failed to load config") } }
Middleware
use axum::{ extract::Request, http::{header, StatusCode}, middleware::Next, response::Response, }; pub async fn auth_middleware( request: Request, next: Next, ) -> Result<Response, StatusCode> { let auth_header = request .headers() .get(header::AUTHORIZATION) .and_then(|h| h.to_str().ok()) .ok_or(StatusCode::UNAUTHORIZED)?; if !auth_header.starts_with("Bearer ") { return Err(StatusCode::UNAUTHORIZED); } // Validate token... Ok(next.run(request).await) } // Apply to routes let protected = Router::new() .route("/me", get(get_current_user)) .layer(axum::middleware::from_fn(auth_middleware));
Build & CI Workflow
Full CI check before commits:
python scripts/check.py
Individual steps:
# Format and lint (auto-fix) python scripts/lint.py --fix # Run tests python scripts/test.py # Security audit python scripts/audit.py # Release build python scripts/build.py --release # Cross-compile for Linux (static binary) python scripts/build.py --release --target x86_64-unknown-linux-musl
For CI/CD pipelines and cross-compilation setup, see ci-cd.md.
TDD Workflow
Use agents for Test-Driven Development:
1. tdd-test-writer → writes failing test (RED) 2. Verify: cargo test → see failure 3. rust-developer → implements minimal code (GREEN + self-review) 4. Verify: cargo test → see pass 5. Repeat for all features 6. code-reviewer → final review before commit/merge
Full cycle example:
# Per feature (TDD cycles) Task[tdd-test-writer]: "GET /users endpoint" # RED Task[rust-developer]: "make test pass" # GREEN + self-review # After all features complete Task[code-reviewer]: "review all changes" # REVIEW git add && git commit # COMMIT
Enable TDD enforcement hook (blocks implementation without failing test):
// .claude/settings.json { "hooks": { "PreToolUse": [{ "matcher": "Task", "hooks": [{ "type": "command", "command": "python3 scripts/tdd_gate.py" }] }] } }
Review checklist (used by rust-developer self-review and code-reviewer):
□ Logic correct? Edge cases handled? □ Security: no injection, no hardcoded secrets? □ Error handling: no unwrap() in handlers? □ Patterns: follows skill guidelines? □ Tests: all pass, coverage adequate?
Anti-patterns
| Don't | Do Instead |
|---|---|
in handlers | Use with proper error types |
everywhere | Use for shared state |
| Raw SQL strings | Use macro |
| Use |
| Blocking code in async | Use |
| Manual JSON | Use derive |