| name | rust-web |
| description | Rust web development expert covering HTTP frameworks (axum, actix), REST API design, handler patterns, state management, middleware, database integration, and domain-driven architecture. |
| metadata | {"triggers":["web","HTTP","REST","API","axum","actix","handler","router","middleware","endpoint"]} |
Framework Selection
| Framework | Characteristics | Recommended For |
|---|
| axum | Modern, Tokio ecosystem, type-safe | New projects (default choice) |
| actix-web | High performance, Actor model | Performance-critical services |
| rocket | Developer-friendly, zero-config | Rapid prototyping |
| warp | Filter-based, functional style | Niche use cases |
Recommendation: Start with axum for most projects. It has excellent ergonomics, strong ecosystem integration, and active development.
Solution Patterns
Pattern 1: Axum Basic Structure
use axum::{routing::get, Router};
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(root))
.route("/users", get(list_users).post(create_user))
.route("/users/:id", get(get_user).delete(delete_user))
.with_state(app_state());
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
axum::serve(listener, app)
.await
.unwrap();
}
Key insight: Routes are type-safe, handlers are async functions.
Pattern 2: Handler Patterns
use axum::{extract::{Path, Query, Json}, http::StatusCode};
async fn get_user(Path(id): Path<u32>) -> Result<Json<User>, StatusCode> {
User::find(id).await
.map(Json)
.ok_or(StatusCode::NOT_FOUND)
}
async fn create_user(
Json(payload): Json<CreateUserRequest>
) -> Result<Json<User>, StatusCode> {
User::create(payload).await
.map(Json)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}
async fn list_users(
Query(params): Query<ListUsersParams>
) -> Json<Vec<User>> {
Json(User::list(params).await)
}
async fn update_user(
Path(id): Path<u32>,
State(db): State<DbPool>,
Json(update): Json<UserUpdate>,
) -> Result<Json<User>, ApiError> {
User::update(&db, id, update).await
.map(Json)
}
When to use: Each extractor pattern for different input types.
Pattern 3: State Management
use std::sync::Arc;
use sqlx::PgPool;
#[derive(Clone)]
struct AppState {
db: PgPool,
config: Arc<Config>,
}
async fn handler(State(state): State<AppState>) -> Json<Response> {
let user = User::fetch(&state.db, 123).await?;
Json(Response { user })
}
let state = AppState {
db: PgPoolOptions::new()
.max_connections(5)
.connect(&db_url)
.await?,
config: Arc::new(load_config()),
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);
When to use: Share database pools, configuration, clients across handlers.
Trade-offs: State must be Clone + Send + Sync + 'static.
Pattern 4: Error Handling
use axum::{
response::{IntoResponse, Response},
http::StatusCode,
Json,
};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ApiError {
#[error("resource not found")]
NotFound,
#[error("invalid input: {0}")]
Validation(String),
#[error("database error")]
Database(#[from] sqlx::Error),
#[error("authentication required")]
Unauthorized,
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let (status, message) = match self {
ApiError::NotFound => (StatusCode::NOT_FOUND, self.to_string()),
ApiError::Validation(msg) => (StatusCode::BAD_REQUEST, msg),
ApiError::Database(e) => {
tracing::error!("database error: {}", e);
(StatusCode::INTERNAL_SERVER_ERROR, "internal error".to_string())
}
ApiError::Unauthorized => (StatusCode::UNAUTHORIZED, self.to_string()),
};
(status, Json(serde_json::json!({
"error": message
}))).into_response()
}
}
When to use: Custom error types for domain-specific failures.
Workflow
Step 1: Choose Framework
Need high performance?
โ actix-web
Want modern ergonomics + Tokio ecosystem?
โ axum (recommended)
Rapid prototyping?
โ rocket
Step 2: Design Handler Signatures
What data comes from?
Path โ Path<T>
Query string โ Query<T>
JSON body โ Json<T>
Headers โ TypedHeader<T>
State โ State<AppState>
Step 3: Implement Error Handling
Library code?
โ Custom error enum + IntoResponse
Application code?
โ anyhow::Error with context
Step 4: Add Middleware
Logging โ tower_http::trace
CORS โ tower_http::cors
Rate limiting โ tower::limit
Authentication โ custom middleware
Middleware Patterns
Logging Middleware
use axum::{
middleware::{self, Next},
http::Request,
response::Response,
};
use std::time::Instant;
async fn log_requests<B>(
req: Request<B>,
next: Next<B>,
) -> Response {
let start = Instant::now();
let method = req.method().clone();
let uri = req.uri().clone();
let response = next.run(req).await;
tracing::info!(
"{} {} {} - {:?}",
method,
uri,
response.status(),
start.elapsed()
);
response
}
let app = Router::new()
.route("/", get(handler))
.layer(middleware::from_fn(log_requests));
Authentication Middleware
use axum::{
middleware,
extract::Request,
http::{StatusCode, header},
};
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())
.ok_or(StatusCode::UNAUTHORIZED)?;
let user = validate_token(auth_header)
.ok_or(StatusCode::UNAUTHORIZED)?;
req.extensions_mut().insert(user);
Ok(next.run(req).await)
}
Database Integration
SQLx Pattern
use sqlx::{PgPool, FromRow};
use chrono::{DateTime, Utc};
#[derive(Debug, FromRow)]
struct User {
id: i32,
name: String,
email: String,
created_at: DateTime<Utc>,
}
async fn get_user(pool: &PgPool, id: i32) -> Result<User, sqlx::Error> {
sqlx::query_as!(
User,
"SELECT id, name, email, created_at FROM users WHERE id = $1",
id
)
.fetch_one(pool)
.await
}
async fn create_user_with_profile(
pool: &PgPool,
user: NewUser,
) -> Result<User, sqlx::Error> {
let mut tx = pool.begin().await?;
let user_id = sqlx::query!(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id",
user.name,
user.email
)
.fetch_one(&mut *tx)
.await?
.id;
sqlx::query!(
"INSERT INTO profiles (user_id, bio) VALUES ($1, $2)",
user_id,
user.bio
)
.execute(&mut *tx)
.await?;
tx.commit().await?;
get_user(pool, user_id).await
}
Best Practices
| Concern | Recommendation |
|---|
| JSON serialization | #[derive(Serialize, Deserialize)] + serde |
| Configuration | config crate + environment variables |
| Logging | tracing + tracing-subscriber |
| Health check | GET /health endpoint returning 200 |
| CORS | tower_http::cors::CorsLayer |
| Rate limiting | tower::limit::RateLimitLayer |
| OpenAPI | utoipa for API documentation |
| Request validation | validator crate with #[validate] |
| Graceful shutdown | tokio::signal for SIGTERM handling |
Common Pitfalls
| Pitfall | Problem | Solution |
|---|
Using Rc for state | Not thread-safe | Use Arc |
Holding locks across .await | Potential deadlock | Minimize lock scope |
| Not handling errors | Handler panics | Implement IntoResponse for errors |
| Large request bodies | Memory pressure | Set body size limits with DefaultBodyLimit |
| Missing CORS headers | Browser blocks requests | Add CorsLayer |
| Synchronous blocking | Blocks executor | Use spawn_blocking for CPU work |
Domain-Driven Project Structure
Recommended structure for medium-to-large projects:
src/
โโโ main.rs # Entry point (load config, start HTTP)
โโโ bootstrap/
โ โโโ mod.rs
โ โโโ app_builder.rs # Global assembly (DB/Cache/Telemetry)
โโโ domains/
โ โโโ user/ # Domain directory with all layers
โ โ โโโ mod.rs
โ โ โโโ http.rs # Routes + handlers + DTO mapping
โ โ โโโ app.rs # Use cases / commands / queries
โ โ โโโ entity.rs # Domain entities
โ โ โโโ value.rs # Value objects
โ โ โโโ policy.rs # Domain rules/policies
โ โ โโโ port.rs # Port definitions (traits)
โ โ โโโ repo.rs # Infrastructure implementation
โ โ โโโ cache.rs # Caching adapter
โ โ โโโ errors.rs # Domain errors
โ โ โโโ tests.rs # Domain tests
โ โโโ auth/
โ โ โโโ mod.rs
โ โ โโโ http.rs
โ โ โโโ app.rs
โ โ โโโ entity.rs
โ โ โโโ policy.rs
โ โ โโโ port.rs
โ โ โโโ repo.rs
โ โ โโโ jwt.rs
โ โ โโโ errors.rs
โ โ โโโ tests.rs
โ โโโ order/
โ โโโ mod.rs
โ โโโ http.rs
โ โโโ app.rs
โ โโโ entity.rs
โ โโโ port.rs
โ โโโ repo.rs
โ โโโ events.rs
โ โโโ errors.rs
โ โโโ tests.rs
โโโ shared/
โ โโโ mod.rs
โ โโโ error.rs # Common error model
โ โโโ result.rs # Unified Result alias
โ โโโ types.rs # Common types (ID/Time)
โ โโโ middleware.rs # Cross-domain middleware
โโโ tests/
โโโ integration/
โโโ fixtures/
Structural Principles:
- Domain-centric: Each domain contains interface/application/domain/infrastructure concerns
- File naming: Single-word filenames clarify responsibility (
app.rs, port.rs, repo.rs, http.rs)
- Loose coupling: Domains collaborate through application-layer interfaces, avoid direct access
- Shared utilities: Common capabilities in
shared/, domain-specific logic stays local
File Responsibilities:
http.rs - HTTP routes, handlers, request/response DTOs
app.rs - Application services, use case orchestration
entity.rs - Domain entities with business logic
port.rs - Port trait definitions (hexagonal architecture)
repo.rs - Repository implementations (database, cache)
errors.rs - Domain-specific error types
Review Checklist
When reviewing web service code:
Verification Commands
cargo check
cargo test
cargo test --test integration
cargo clippy -- -D warnings
cargo run
cargo build --release
DATABASE_URL=postgres://localhost cargo run
Performance Optimization
Connection Pooling
use sqlx::postgres::PgPoolOptions;
let pool = PgPoolOptions::new()
.max_connections(100)
.min_connections(10)
.acquire_timeout(Duration::from_secs(5))
.idle_timeout(Duration::from_secs(600))
.connect(&database_url)
.await?;
Response Compression
use tower_http::compression::CompressionLayer;
let app = Router::new()
.route("/", get(handler))
.layer(CompressionLayer::new());
Caching Headers
use axum::http::{header, HeaderMap};
async fn cached_handler() -> (HeaderMap, Json<Data>) {
let mut headers = HeaderMap::new();
headers.insert(
header::CACHE_CONTROL,
"public, max-age=3600".parse().unwrap(),
);
(headers, Json(get_data()))
}
Related Skills
- rust-async - Async patterns for handlers
- rust-concurrency - Thread safety in web services
- rust-database - Database integration patterns
- rust-error - Error handling strategies
- rust-auth - Authentication and authorization
- rust-middleware - Middleware patterns
- rust-observability - Logging and metrics
Localized Reference
- Chinese version: SKILL_ZH.md - ๅฎๆดไธญๆ็ๆฌ๏ผๅ
ๅซๆๆๅ
ๅฎน