REST API Architecture

Building RESTful APIs with Rust using modern frameworks.

Intermediateā±ļø 50 minšŸ“š Prerequisites: 1

REST API Architecture

Building production-ready REST APIs with Rust requires proper structure and framework selection.

Popular Rust Web Frameworks

1. Axum (Recommended)

Modern, async-first framework built on Tokio

TOML
[dependencies]
axum = "0.7"
tokio = { version = "1.0", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["cors"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

2. Actix Web

High-performance, actor-based framework

TOML
[dependencies]
actix-web = "4.4"
actix-rt = "2.9"
serde = { version = "1.0", features = ["derive"] }

3. Rocket

Easy-to-use, type-safe framework

TOML
[dependencies]
rocket = { version = "0.5", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }

Axum API Structure

src/
ā”œā”€ā”€ main.rs
ā”œā”€ā”€ lib.rs
ā”œā”€ā”€ api/
│   ā”œā”€ā”€ mod.rs
│   ā”œā”€ā”€ routes.rs          # Route definitions
│   ā”œā”€ā”€ handlers/          # Request handlers
│   │   ā”œā”€ā”€ mod.rs
│   │   ā”œā”€ā”€ user_handler.rs
│   │   └── transaction_handler.rs
│   ā”œā”€ā”€ middleware/        # Custom middleware
│   │   ā”œā”€ā”€ mod.rs
│   │   ā”œā”€ā”€ auth.rs
│   │   └── logging.rs
│   └── extractors/        # Custom extractors
│       └── mod.rs
ā”œā”€ā”€ services/
│   ā”œā”€ā”€ mod.rs
│   └── user_service.rs
ā”œā”€ā”€ repositories/
│   ā”œā”€ā”€ mod.rs
│   └── user_repository.rs
ā”œā”€ā”€ models/
│   ā”œā”€ā”€ mod.rs
│   ā”œā”€ā”€ user.rs
│   └── dto.rs            # Data Transfer Objects
ā”œā”€ā”€ database/
│   ā”œā”€ā”€ mod.rs
│   └── connection.rs
└── config/
    └── mod.rs

Complete Axum Example

RUST
// src/main.rs
use axum::{Router, routing::get, Json};
use serde_json::{Value, json};

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(root))
        .route("/health", get(health_check));
    
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn root() -> Json<Value> {
    Json(json!({ "message": "Hello, World!" }))
}

async fn health_check() -> Json<Value> {
    Json(json!({ "status": "healthy" }))
}

Route Organization

RUST
// src/api/routes.rs
use axum::{Router, routing::{get, post, put, delete}};
use crate::api::handlers::{user_handler, transaction_handler};

pub fn create_router() -> Router {
    Router::new()
        .nest("/api/v1", api_v1_routes())
        .route("/health", get(health_check))
}

fn api_v1_routes() -> Router {
    Router::new()
        .nest("/users", user_routes())
        .nest("/transactions", transaction_routes())
}

fn user_routes() -> Router {
    Router::new()
        .route("/", get(user_handler::list_users))
        .route("/", post(user_handler::create_user))
        .route("/:id", get(user_handler::get_user))
        .route("/:id", put(user_handler::update_user))
        .route("/:id", delete(user_handler::delete_user))
}

Handler Implementation

RUST
// src/api/handlers/user_handler.rs
use axum::{extract::{Path, State}, Json, response::Json as ResponseJson};
use crate::{services::UserService, models::dto::CreateUserRequest};

pub async fn create_user(
    State(service): State<UserService>,
    Json(payload): Json<CreateUserRequest>
) -> Result<ResponseJson<User>, AppError> {
    let user = service.create_user(payload).await?;
    Ok(ResponseJson(user))
}

pub async fn get_user(
    State(service): State<UserService>,
    Path(id): Path<u64>
) -> Result<ResponseJson<User>, AppError> {
    let user = service.get_user(id).await?;
    Ok(ResponseJson(user))
}

Middleware

RUST
// src/api/middleware/auth.rs
use axum::{extract::Request, middleware::Next, response::Response};
use tower::ServiceBuilder;
use tower_http::auth::RequireAuthorizationLayer;

pub async fn auth_middleware(
    mut request: Request,
    next: Next
) -> Response {
    // Extract and validate token
    // Add user to request extensions
    next.run(request).await
}

// Apply middleware
let app = Router::new()
    .route("/api/protected", get(protected_route))
    .layer(ServiceBuilder::new()
        .layer(RequireAuthorizationLayer::bearer("secret"))
    );

Error Handling

RUST
// src/error.rs
use axum::{response::{Response, IntoResponse}, http::StatusCode};

#[derive(Debug)]
pub enum AppError {
    NotFound,
    ValidationError(String),
    DatabaseError(String),
    Unauthorized,
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AppError::NotFound => (StatusCode::NOT_FOUND, "Resource not found"),
            AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, &msg),
            AppError::DatabaseError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, &msg),
            AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"),
        };
        
        (status, Json(json!({ "error": message }))).into_response()
    }
}

Database Integration

RUST
// src/database/connection.rs
use sqlx::{PgPool, postgres::PgPoolOptions};

pub async fn create_pool(database_url: &str) -> Result<PgPool, sqlx::Error> {
    PgPoolOptions::new()
        .max_connections(10)
        .connect(database_url)
        .await
}

// In main.rs
let pool = create_pool(&database_url).await?;
let app = Router::new()
    .route("/api/users", get(get_users))
    .with_state(pool);

Best Practices

  • Route organization: Group related routes
  • State management: Use State extractor for dependencies
  • Error handling: Centralized error types
  • Validation: Validate input in handlers
  • Middleware: Use for cross-cutting concerns
  • Documentation: Use OpenAPI/Swagger
  • Testing: Integration tests for API endpoints

Code Examples

Axum API Example

Basic Axum REST API structure

RUST
struct AppState {
}
fn main() {
    println!("REST API with Axum:");
    println!("1. Router: Define routes");
    println!("2. Handlers: Process requests");
    println!("3. State: Share dependencies");
    println!("4. Middleware: Cross-cutting concerns");
    println!("5. Extractors: Parse request data");
}

Explanation:

Axum provides a modern, type-safe API for building REST APIs. It uses async/await and integrates well with Tokio.

API Project Structure

Organizing API code

RUST
fn main() {
    println!("API Architecture:");
    println!("routes → handlers → services → repositories");
    println!("Each layer has clear responsibility");
}

Explanation:

Well-organized API code separates concerns: routes define endpoints, handlers process requests, services contain business logic, and repositories handle data access.

Exercises

API Route

Create a simple API route structure!

Medium

Starter Code:

RUST
fn main() {
    println!("API routes created!");
}