Time to put it all together! This chapter walks through building production-ready applications using the intermediate concepts you've learned.
// Project structure
project/
├── Cargo.toml
├── src/
│ ├── main.rs
│ ├── api/
│ │ ├── mod.rs
│ │ ├── handlers.rs
│ │ └── middleware.rs
│ ├── db/
│ │ ├── mod.rs
│ │ └── pool.rs
│ ├── models/
│ │ ├── mod.rs
│ │ └── user.rs
│ └── error.rs
└── tests/
└── integration_test.rs
// src/error.rs
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ApiError {
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("Not found: {0}")]
NotFound(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("Unauthorized")]
Unauthorized,
}
impl axum::response::IntoResponse for ApiError {
fn into_response(self) -> axum::response::Response {
use axum::http::StatusCode;
use axum::Json;
use serde_json::json;
let (status, message) = match self {
ApiError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
ApiError::NotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
ApiError::Validation(_) => (StatusCode::BAD_REQUEST, self.to_string()),
ApiError::Unauthorized => (StatusCode::UNAUTHORIZED, self.to_string()),
};
(status, Json(json!({ "error": message }))).into_response()
}
}
// src/db/pool.rs
use sqlx::{PgPool, postgres::PgPoolOptions};
use std::time::Duration;
pub async fn create_pool(database_url: &str) -> Result<PgPool, sqlx::Error> {
PgPoolOptions::new()
.max_connections(10)
.acquire_timeout(Duration::from_secs(3))
.connect(database_url)
.await
}
// src/models/user.rs
use sqlx::FromRow;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct User {
pub id: i64,
pub name: String,
pub email: String,
pub created_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Deserialize)]
pub struct CreateUser {
pub name: String,
pub email: String,
}
pub async fn create_user(pool: &PgPool, user: CreateUser) -> Result<User, sqlx::Error> {
sqlx::query_as::<_, User>(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *"
)
.bind(&user.name)
.bind(&user.email)
.fetch_one(pool)
.await
}
pub async fn get_user(pool: &PgPool, id: i64) -> Result<User, sqlx::Error> {
sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
.bind(id)
.fetch_one(pool)
.await
}
// src/api/handlers.rs
use axum::{
extract::{Path, State},
Json,
};
use std::sync::Arc;
use crate::{error::ApiError, models::*};
pub type AppState = Arc<sqlx::PgPool>;
pub async fn create_user_handler(
State(pool): State<AppState>,
Json(payload): Json<CreateUser>,
) -> Result<Json<User>, ApiError> {
let user = create_user(&pool, payload).await?;
Ok(Json(user))
}
pub async fn get_user_handler(
State(pool): State<AppState>,
Path(id): Path<i64>,
) -> Result<Json<User>, ApiError> {
let user = get_user(&pool, id)
.await
.map_err(|_| ApiError::NotFound(format!("User {} not found", id)))?;
Ok(Json(user))
}
// src/main.rs
use axum::{
routing::{get, post},
Router,
};
use std::sync::Arc;
#[tokio::main]
async fn main() {
let pool = db::create_pool(&std::env::var("DATABASE_URL").unwrap())
.await
.unwrap();
let app_state = Arc::new(pool);
let app = Router::new()
.route("/users", post(api::create_user_handler))
.route("/users/:id", get(api::get_user_handler))
.with_state(app_state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
// Cargo.toml
[dependencies]
clap = { version = "4", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
anyhow = "1"
colored = "2"
// src/cli.rs
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "mytool")]
#[command(about = "A modern CLI tool", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(short, long, global = true)]
verbose: bool,
}
#[derive(Subcommand)]
enum Commands {
/// Initialize a new project
Init {
#[arg(short, long)]
name: String,
#[arg(short, long, default_value = ".")]
path: String,
},
/// Build the project
Build {
#[arg(short, long)]
release: bool,
},
/// Run tests
Test {
#[arg(short, long)]
coverage: bool,
},
}
use colored::*;
fn print_success(msg: &str) {
println!("{} {}", "✓".green().bold(), msg);
}
fn print_error(msg: &str) {
eprintln!("{} {}", "✗".red().bold(), msg);
}
fn print_info(msg: &str) {
println!("{} {}", "ℹ".blue().bold(), msg);
}
async fn run_init(name: String, path: String) -> anyhow::Result<()> {
print_info(&format!("Initializing project '{}'...", name));
tokio::fs::create_dir_all(&path).await?;
print_success(&format!("Created directory: {}", path));
let config = serde_json::json!({
"name": name,
"version": "0.1.0",
});
tokio::fs::write(
format!("{}/config.json", path),
serde_json::to_string_pretty(&config)?
).await?;
print_success("Project initialized successfully!");
Ok(())
}
use axum::{
extract::ws::{WebSocket, WebSocketUpgrade},
response::Response,
routing::get,
Router,
};
use std::sync::Arc;
use tokio::sync::broadcast;
type Tx = broadcast::Sender<String>;
type Rx = broadcast::Receiver<String>;
#[derive(Clone)]
struct AppState {
tx: Tx,
}
async fn ws_handler(
ws: WebSocketUpgrade,
axum::extract::State(state): axum::extract::State<AppState>,
) -> Response {
ws.on_upgrade(|socket| handle_socket(socket, state))
}
async fn handle_socket(socket: WebSocket, state: AppState) {
let (mut sender, mut receiver) = socket.split();
let mut rx = state.tx.subscribe();
// Spawn task to forward broadcast messages to this client
let mut send_task = tokio::spawn(async move {
while let Ok(msg) = rx.recv().await {
if sender.send(msg.into()).await.is_err() {
break;
}
}
});
// Receive messages from this client and broadcast
let tx = state.tx.clone();
let mut recv_task = tokio::spawn(async move {
while let Some(Ok(msg)) = receiver.next().await {
if let axum::extract::ws::Message::Text(text) = msg {
let _ = tx.send(text);
}
}
});
// Wait for either task to finish
tokio::select! {
_ = (&mut send_task) => recv_task.abort(),
_ = (&mut recv_task) => send_task.abort(),
}
}
#[tokio::main]
async fn main() {
let (tx, _rx) = broadcast::channel(100);
let state = AppState { tx };
let app = Router::new()
.route("/ws", get(ws_handler))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
use tokio::sync::mpsc;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Serialize, Deserialize)]
enum Job {
SendEmail { to: String, subject: String, body: String },
GenerateReport { user_id: u64 },
ProcessImage { path: String },
}
struct Worker {
id: usize,
receiver: mpsc::Receiver<Job>,
}
impl Worker {
async fn run(mut self) {
while let Some(job) = self.receiver.recv().await {
println!("Worker {} processing job: {:?}", self.id, job);
match job {
Job::SendEmail { to, subject, body } => {
self.send_email(&to, &subject, &body).await;
}
Job::GenerateReport { user_id } => {
self.generate_report(user_id).await;
}
Job::ProcessImage { path } => {
self.process_image(&path).await;
}
}
}
}
async fn send_email(&self, to: &str, subject: &str, body: &str) {
tokio::time::sleep(Duration::from_secs(1)).await;
println!("Email sent to {}", to);
}
async fn generate_report(&self, user_id: u64) {
tokio::time::sleep(Duration::from_secs(2)).await;
println!("Report generated for user {}", user_id);
}
async fn process_image(&self, path: &str) {
tokio::time::sleep(Duration::from_secs(3)).await;
println!("Image processed: {}", path);
}
}
struct JobQueue {
sender: mpsc::Sender<Job>,
}
impl JobQueue {
fn new(num_workers: usize) -> Self {
let (sender, receiver) = mpsc::channel(100);
// Create worker pool
for id in 0..num_workers {
let worker_receiver = receiver.resubscribe();
let worker = Worker {
id,
receiver: worker_receiver,
};
tokio::spawn(async move {
worker.run().await;
});
}
JobQueue { sender }
}
async fn enqueue(&self, job: Job) -> Result<(), String> {
self.sender
.send(job)
.await
.map_err(|_| "Failed to enqueue job".to_string())
}
}
#[tokio::main]
async fn main() {
let queue = JobQueue::new(4);
// Enqueue jobs
queue.enqueue(Job::SendEmail {
to: "user@example.com".to_string(),
subject: "Hello".to_string(),
body: "Test email".to_string(),
}).await.unwrap();
queue.enqueue(Job::GenerateReport { user_id: 123 }).await.unwrap();
tokio::time::sleep(Duration::from_secs(10)).await;
}
# Dockerfile FROM rust:1.75 as builder WORKDIR /app COPY . . RUN cargo build --release FROM debian:bookworm-slim RUN apt-get update && apt-get install -y libssl3 ca-certificates COPY --from=builder /app/target/release/myapp /usr/local/bin/myapp CMD ["myapp"]
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Run tests
run: cargo test --all-features
- name: Run clippy
run: cargo clippy -- -D warnings
- name: Check formatting
run: cargo fmt -- --check