Rust + Axum Backend Mastery
Production-ready patterns for building scalable Rust backends with Axum and PostgreSQL.
When to Use This Skill
- Building REST APIs or GraphQL with Axum
- Designing database schemas with SQLx + PostgreSQL
- Implementing authentication (JWT, OAuth 2.1)
- Writing async code with Tokio
- Creating middleware and extractors
- Testing Axum applications
- Deploying to production (Docker, Kubernetes)
- Performance optimization and monitoring
Quick Start
Minimal Axum Server
use axum::{routing::get, Router};
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "Hello, Axum!" }));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Essential Dependencies (Cargo.toml)
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "trace"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "uuid", "time"] }
uuid = { version = "1", features = ["v4", "serde"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
thiserror = "1"
anyhow = "1"
Reference Navigation
Core Rust Patterns
Axum Framework (70%)
Database (PostgreSQL)
Architecture
Security & Auth
Testing & Quality
DevOps & Production
Decision Guide
When to Choose Axum (70% - Primary)
✅ Choose Axum when:
- Building new Rust web projects
- Need tower ecosystem compatibility
- Want ergonomic, type-safe extractors
- Prefer modular, composable design
- Need excellent async performance
When to Consider Alternatives (30%)
Actix-web - When you need:
- Maximum raw performance (benchmarks leader)
- Actor model for complex state
- Established ecosystem with more examples
Rocket - When you need:
- Simplest learning curve
- Most "magical" developer experience
- Rapid prototyping
Core Patterns Summary
Error Handling
use axum::{http::StatusCode, response::IntoResponse, Json};
use serde_json::json;
pub enum AppError {
NotFound(String),
Database(sqlx::Error),
Unauthorized,
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
let (status, message) = match self {
Self::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
Self::Database(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
Self::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized".into()),
};
(status, Json(json!({ "error": message }))).into_response()
}
}
Handler Pattern
use axum::{extract::{Path, State}, Json};
use uuid::Uuid;
async fn get_user(
State(pool): State<PgPool>,
Path(id): Path<Uuid>,
) -> Result<Json<User>, AppError> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_optional(&pool)
.await?
.ok_or_else(|| AppError::NotFound("User not found".into()))?;
Ok(Json(user))
}
State Management
use std::sync::Arc;
use sqlx::PgPool;
#[derive(Clone)]
pub struct AppState {
pub db: PgPool,
pub config: Arc<Config>,
}
let app = Router::new()
.route("/users/:id", get(get_user))
.with_state(AppState { db: pool, config: Arc::new(config) });
Examples
Best Practices Checklist
API Design
Database
Security
Testing
Production
Resources