Rust

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

7 min de leitura

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

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

<h2>Introdução ao Axum e sua Arquitetura</h2>

<p>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.</p>

<p>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.</p>

<h2>Configurando seu Primeiro Projeto</h2>

<p>Comece criando um novo projeto Rust e adicionando as dependências essenciais no <code>Cargo.toml</code>:</p>

<pre><code class="language-toml">[package]

name = &quot;api-axum&quot;

version = &quot;0.1.0&quot;

edition = &quot;2021&quot;

[dependencies]

axum = &quot;0.7&quot;

tokio = { version = &quot;1&quot;, features = [&quot;full&quot;] }

serde = { version = &quot;1.0&quot;, features = [&quot;derive&quot;] }

serde_json = &quot;1.0&quot;</code></pre>

<p>Agora crie uma API simples com um endpoint que retorna JSON:</p>

<pre><code class="language-rust">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&lt;User&gt;) -&gt; Json&lt;User&gt; {

Json(User {

id: 1,

name: payload.name,

email: payload.email,

})

}

async fn list_users() -&gt; Json&lt;Vec&lt;User&gt;&gt; {

Json(vec![

User {

id: 1,

name: &quot;Alice&quot;.to_string(),

email: &quot;alice@example.com&quot;.to_string(),

},

])

}

#[tokio::main]

async fn main() {

let app = Router::new()

.route(&quot;/users&quot;, post(create_user))

.route(&quot;/users&quot;, 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();

}</code></pre>

<p>Execute com <code>cargo run</code> e teste com curl: <code>curl -X POST http://localhost:3000/users -H &quot;Content-Type: application/json&quot; -d &#039;{&quot;id&quot;:1,&quot;name&quot;:&quot;Bob&quot;,&quot;email&quot;:&quot;bob@test.com&quot;}&#039;</code></p>

<h2>Roteamento Avançado e Handlers Tipados</h2>

<h3>Parâmetros de Path e Query</h3>

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

<pre><code class="language-rust">use axum::extract::Path;

async fn get_user(Path(user_id): Path&lt;u32&gt;) -&gt; Json&lt;User&gt; {

Json(User {

id: user_id,

name: &quot;Alice&quot;.to_string(),

email: &quot;alice@example.com&quot;.to_string(),

})

}

let app = Router::new()

.route(&quot;/users/:id&quot;, get(get_user));</code></pre>

<p>Para query parameters, use <code>Query</code>:</p>

<pre><code class="language-rust">use axum::extract::Query;

use serde::Deserialize;

#[derive(Deserialize)]

struct ListParams {

limit: Option&lt;u32&gt;,

offset: Option&lt;u32&gt;,

}

async fn list_users_paginated(

Query(params): Query&lt;ListParams&gt;,

) -&gt; Json&lt;Vec&lt;User&gt;&gt; {

let limit = params.limit.unwrap_or(10);

// Implementar lógica de paginação

Json(vec![])

}

let app = Router::new()

.route(&quot;/users&quot;, get(list_users_paginated));</code></pre>

<h3>Estados Compartilhados</h3>

<p>Aplicações reais precisam compartilhar estado entre handlers. Use <code>State</code> para injetar dados:</p>

<pre><code class="language-rust">use axum::extract::State;

use std::sync::Arc;

use std::sync::Mutex;

type SharedUsers = Arc&lt;Mutex&lt;Vec&lt;User&gt;&gt;&gt;;

async fn create_user_with_state(

State(users): State&lt;SharedUsers&gt;,

Json(payload): Json&lt;User&gt;,

) -&gt; Json&lt;User&gt; {

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(&quot;/users&quot;, post(create_user_with_state))

.with_state(users_state);

// ... rest do código

}</code></pre>

<h2>Tratamento de Erros e Middlewares</h2>

<h3>Custom Error Responses</h3>

<p>Defina tipos de erro que implementem <code>IntoResponse</code> para respostas HTTP customizadas:</p>

<pre><code class="language-rust">use axum::{

http::StatusCode,

response::{IntoResponse, Response},

Json,

};

enum ApiError {

UserNotFound,

InvalidInput,

}

impl IntoResponse for ApiError {

fn into_response(self) -&gt; Response {

let (status, error_message) = match self {

ApiError::UserNotFound =&gt; (StatusCode::NOT_FOUND, &quot;User not found&quot;),

ApiError::InvalidInput =&gt; (StatusCode::BAD_REQUEST, &quot;Invalid input&quot;),

};

(status, Json(serde_json::json!({&quot;error&quot;: error_message}))).into_response()

}

}

async fn get_user_safe(

Path(user_id): Path&lt;u32&gt;,

) -&gt; Result&lt;Json&lt;User&gt;, ApiError&gt; {

if user_id == 0 {

return Err(ApiError::InvalidInput);

}

Ok(Json(User {

id: user_id,

name: &quot;Alice&quot;.to_string(),

email: &quot;alice@example.com&quot;.to_string(),

}))

}</code></pre>

<h3>Middlewares com Tower</h3>

<p>Axum integra perfeitamente com Tower para adicionar funcionalidades cross-cutting:</p>

<pre><code class="language-rust">use tower_http::cors::CorsLayer;

use tower_http::trace::TraceLayer;

let app = Router::new()

.route(&quot;/users&quot;, post(create_user))

.route(&quot;/users/:id&quot;, get(get_user_safe))

.layer(TraceLayer::new_for_http())

.layer(CorsLayer::permissive());</code></pre>

<p>Adicione ao <code>Cargo.toml</code>: <code>tower-http = { version = &quot;0.5&quot;, features = [&quot;cors&quot;, &quot;trace&quot;] }</code></p>

<h2>Conclusão</h2>

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

<h2>Referências</h2>

<ul>

<li><a href="https://docs.rs/axum/latest/axum/" target="_blank" rel="noopener noreferrer">Documentação oficial Axum</a></li>

<li><a href="https://tokio.rs/" target="_blank" rel="noopener noreferrer">Tokio Runtime Guide</a></li>

<li><a href="https://docs.rs/tower/latest/tower/" target="_blank" rel="noopener noreferrer">Tower Middleware Documentation</a></li>

<li><a href="https://rust-lang.github.io/async-book/" target="_blank" rel="noopener noreferrer">Rust Book - Async Programming</a></li>

<li><a href="https://serde.rs/" target="_blank" rel="noopener noreferrer">Serde Serialization Framework</a></li>

</ul>

Comentários

Mais em Rust

Guia Completo de Box<T> em Rust: Alocação Explícita no Heap
Guia Completo de Box<T> em Rust: Alocação Explícita no Heap

O que é Box e Por Que Usar? Box é um tipo de dado inteligente (smart pointer)...

Como Usar Workspaces no Cargo: Organizando Projetos Grandes em Rust em Produção
Como Usar Workspaces no Cargo: Organizando Projetos Grandes em Rust em Produção

O que são Workspaces no Cargo? Um workspace no Cargo é um mecanismo para orga...

Como Usar Autenticação JWT em APIs Rust com Axum em Produção
Como Usar Autenticação JWT em APIs Rust com Axum em Produção

Fundamentos de JWT e Segurança em APIs JSON Web Token (JWT) é um padrão abert...