Rust Admin

Construindo APIs REST com Axum em Rust: Do Básico ao Avançado Já leu

Introdução ao Axum e sua Arquitetura Axum é um framework web moderno construído sobre a stack assíncrona de Rust, particularmente o Tokio. Diferentemente de frameworks tradicionais, Axum trabalha com composição de componentes através de towers e middlewares, permitindo máxima flexibilidade e performance. O framework é mantido pela equipe Tokio e representa o estado da arte em desenvolvimento web com Rust, sendo ideal para APIs REST que demandam alta concorrência e baixa latência. A arquitetura de Axum segue o padrão de roteadores compostos e handlers tipados. Cada rota é associada a um handler function que recebe extratos da requisição (como JSON bodies ou parâmetros de path) de forma type-safe. Isso significa que erros de tipo são capturados em tempo de compilação, não em runtime como em muitas linguagens dinâmicas. Configurando seu Primeiro Projeto Comece criando um novo projeto Rust e adicionando as dependências essenciais no : Agora crie uma API simples com um endpoint que retorna JSON: Execute com e teste

Introdução ao Axum e sua Arquitetura

Axum é um framework web moderno construído sobre a stack assíncrona de Rust, particularmente o Tokio. Diferentemente de frameworks tradicionais, Axum trabalha com composição de componentes através de towers e middlewares, permitindo máxima flexibilidade e performance. O framework é mantido pela equipe Tokio e representa o estado da arte em desenvolvimento web com Rust, sendo ideal para APIs REST que demandam alta concorrência e baixa latência.

A arquitetura de Axum segue o padrão de roteadores compostos e handlers tipados. Cada rota é associada a um handler function que recebe extratos da requisição (como JSON bodies ou parâmetros de path) de forma type-safe. Isso significa que erros de tipo são capturados em tempo de compilação, não em runtime como em muitas linguagens dinâmicas.

Configurando seu Primeiro Projeto

Comece criando um novo projeto Rust e adicionando as dependências essenciais no Cargo.toml:

[package]
name = "api-axum"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Agora crie uma API simples com um endpoint que retorna JSON:

use axum::{
    routing::{get, post},
    Json, Router,
};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;

#[derive(Serialize, Deserialize)]
struct User {
    id: u32,
    name: String,
    email: String,
}

async fn create_user(Json(payload): Json<User>) -> Json<User> {
    Json(User {
        id: 1,
        name: payload.name,
        email: payload.email,
    })
}

async fn list_users() -> Json<Vec<User>> {
    Json(vec![
        User {
            id: 1,
            name: "Alice".to_string(),
            email: "alice@example.com".to_string(),
        },
    ])
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/users", post(create_user))
        .route("/users", get(list_users));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();

    axum::serve(listener, app).await.unwrap();
}

Execute com cargo run e teste com curl: curl -X POST http://localhost:3000/users -H "Content-Type: application/json" -d '{"id":1,"name":"Bob","email":"bob@test.com"}'

Roteamento Avançado e Handlers Tipados

Parâmetros de Path e Query

Axum permite extrair parâmetros diretamente através do sistema de tipos. Para parâmetros de path, use :param na rota:

use axum::extract::Path;

async fn get_user(Path(user_id): Path<u32>) -> Json<User> {
    Json(User {
        id: user_id,
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    })
}

let app = Router::new()
    .route("/users/:id", get(get_user));

Para query parameters, use Query:

use axum::extract::Query;
use serde::Deserialize;

#[derive(Deserialize)]
struct ListParams {
    limit: Option<u32>,
    offset: Option<u32>,
}

async fn list_users_paginated(
    Query(params): Query<ListParams>,
) -> Json<Vec<User>> {
    let limit = params.limit.unwrap_or(10);
    // Implementar lógica de paginação
    Json(vec![])
}

let app = Router::new()
    .route("/users", get(list_users_paginated));

Estados Compartilhados

Aplicações reais precisam compartilhar estado entre handlers. Use State para injetar dados:

use axum::extract::State;
use std::sync::Arc;
use std::sync::Mutex;

type SharedUsers = Arc<Mutex<Vec<User>>>;

async fn create_user_with_state(
    State(users): State<SharedUsers>,
    Json(payload): Json<User>,
) -> Json<User> {
    let mut users_lock = users.lock().unwrap();
    users_lock.push(payload.clone());
    Json(payload)
}

#[tokio::main]
async fn main() {
    let users_state: SharedUsers = Arc::new(Mutex::new(vec![]));

    let app = Router::new()
        .route("/users", post(create_user_with_state))
        .with_state(users_state);

    // ... rest do código
}

Tratamento de Erros e Middlewares

Custom Error Responses

Defina tipos de erro que implementem IntoResponse para respostas HTTP customizadas:

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};

enum ApiError {
    UserNotFound,
    InvalidInput,
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let (status, error_message) = match self {
            ApiError::UserNotFound => (StatusCode::NOT_FOUND, "User not found"),
            ApiError::InvalidInput => (StatusCode::BAD_REQUEST, "Invalid input"),
        };

        (status, Json(serde_json::json!({"error": error_message}))).into_response()
    }
}

async fn get_user_safe(
    Path(user_id): Path<u32>,
) -> Result<Json<User>, ApiError> {
    if user_id == 0 {
        return Err(ApiError::InvalidInput);
    }
    Ok(Json(User {
        id: user_id,
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    }))
}

Middlewares com Tower

Axum integra perfeitamente com Tower para adicionar funcionalidades cross-cutting:

use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;

let app = Router::new()
    .route("/users", post(create_user))
    .route("/users/:id", get(get_user_safe))
    .layer(TraceLayer::new_for_http())
    .layer(CorsLayer::permissive());

Adicione ao Cargo.toml: tower-http = { version = "0.5", features = ["cors", "trace"] }

Conclusão

Você aprendeu que Axum oferece type-safety e composição elegante para construir APIs REST robustas, permitindo que erros sejam capturados em compilação. O roteamento é intuitivo e as extrações de dados automáticas reduzem boilerplate significativamente. Finalmente, a integração com Tower e o ecosistema Tokio posiciona Axum como escolha ideal para aplicações de alta performance em produção.

Referências


Artigos relacionados