Introdução
O Axum consolidou-se como o framework web mais popular do ecossistema Rust em 2026. Criado pela equipe do Tokio, ele combina a performance bruta do Rust com uma API ergonômica que facilita a construção de APIs REST, microserviços e aplicações web de produção. Diferente de outros frameworks que reinventam a roda, o Axum é construído sobre o Tokio e o Tower — o mesmo stack que alimenta serviços de infraestrutura crítica em empresas como AWS, Cloudflare e Discord.
Se você quer entender por que tantas empresas estão adotando Rust para seus backends — como discutimos em Rust em Produção — o Axum é o ponto de partida ideal. E se você vem do mundo Go, onde net/http e Gin dominam, vai notar semelhanças no modelo de handlers — Go também tem excelentes frameworks web, compare em Golang Brasil.
Por que Axum se Tornou o Framework Mais Popular
Filosofia de Design
O Axum segue uma filosofia minimalista e composicional. Em vez de criar abstrações próprias para middleware, extração de dados e gerenciamento de estado, ele aproveita o ecossistema Tower já existente. Isso significa que qualquer middleware escrito para Tower funciona automaticamente com Axum — e vice-versa.
Os principais diferenciais que impulsionaram sua adoção:
- Sem macros mágicas: diferente do Rocket e Actix-Web, os handlers do Axum são funções async comuns
- Tipagem forte nos extractors: erros de extração são detectados em tempo de compilação
- Integração nativa com Tokio: sem overhead de adaptação entre runtimes
- Ecossistema Tower: acesso a centenas de middlewares prontos para produção
Configuração Inicial e Routing Básico
Vamos começar com um projeto mínimo. Adicione as dependências no Cargo.toml:
// Cargo.toml
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Agora, o servidor básico com rotas:
use axum::{
routing::{get, post},
Router, Json,
};
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
struct Mensagem {
texto: String,
}
async fn raiz() -> Json<Mensagem> {
Json(Mensagem {
texto: "Bem-vindo à API Rust com Axum!".into(),
})
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(raiz))
.route("/usuarios", post(criar_usuario));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("Servidor rodando em http://localhost:3000");
axum::serve(listener, app).await.unwrap();
}
O modelo de routing do Axum é declarativo e intuitivo. Cada rota associa um caminho a um handler — uma função async que recebe extractors e retorna uma resposta.
Extractors: O Coração do Axum
Os extractors são o mecanismo que torna o Axum tão ergonômico. Eles extraem dados da requisição HTTP e os entregam tipados ao seu handler. Se a extração falhar, o Axum retorna automaticamente o erro HTTP apropriado.
Path, Query e Json
use axum::extract::{Path, Query, State};
use std::collections::HashMap;
// Extrai parâmetros da URL: /usuarios/42
async fn buscar_usuario(Path(id): Path<u64>) -> String {
format!("Buscando usuário com ID: {id}")
}
// Extrai query params: /busca?termo=rust&pagina=1
async fn buscar(Query(params): Query<HashMap<String, String>>) -> String {
let termo = params.get("termo").unwrap_or(&"".into());
format!("Buscando por: {termo}")
}
// Extrai e valida JSON do body
#[derive(Deserialize)]
struct NovoUsuario {
nome: String,
email: String,
}
async fn criar_usuario(Json(dados): Json<NovoUsuario>) -> Json<Mensagem> {
Json(Mensagem {
texto: format!("Usuário {} criado com sucesso!", dados.nome),
})
}
State: Compartilhando Estado entre Handlers
Para compartilhar conexões de banco de dados, configurações ou caches, o Axum usa o extractor State:
use axum::extract::State;
use std::sync::Arc;
struct AppState {
db_url: String,
nome_app: String,
}
#[tokio::main]
async fn main() {
let estado = Arc::new(AppState {
db_url: "postgres://localhost/meuapp".into(),
nome_app: "Minha API Rust".into(),
});
let app = Router::new()
.route("/info", get(info))
.with_state(estado);
// ...
}
async fn info(State(estado): State<Arc<AppState>>) -> String {
format!("App: {}", estado.nome_app)
}
Middleware com Tower
A integração com Tower é uma das maiores vantagens do Axum. Você pode aplicar middleware por rota, por grupo de rotas ou globalmente. O ecossistema async do Rust em 2026 torna isso ainda mais poderoso.
use axum::middleware;
use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;
use std::time::Duration;
let app = Router::new()
.route("/api/usuarios", get(listar_usuarios))
.route("/api/usuarios/:id", get(buscar_usuario))
.layer(CorsLayer::permissive())
.layer(TraceLayer::new_for_http())
.layer(middleware::from_fn(autenticacao_middleware));
O tower-http fornece middlewares prontos para CORS, compressão, rate limiting, logging e muito mais — todos testados em produção por milhares de serviços.
Tratamento de Erros Robusto
O Axum permite criar tipos de erro personalizados que se convertem automaticamente em respostas HTTP:
use axum::{http::StatusCode, response::IntoResponse};
enum AppError {
NaoEncontrado,
ErroInterno(String),
NaoAutorizado,
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
let (status, mensagem) = match self {
AppError::NaoEncontrado => (
StatusCode::NOT_FOUND,
"Recurso não encontrado".to_string(),
),
AppError::ErroInterno(msg) => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("Erro interno: {msg}"),
),
AppError::NaoAutorizado => (
StatusCode::UNAUTHORIZED,
"Acesso não autorizado".to_string(),
),
};
(status, Json(serde_json::json!({ "erro": mensagem }))).into_response()
}
}
Integração com Banco de Dados via SQLx
Para aplicações reais, a integração com banco de dados é essencial. O SQLx é a escolha natural — oferece queries verificadas em tempo de compilação:
use sqlx::PgPool;
struct AppState {
pool: PgPool,
}
async fn listar_usuarios(
State(state): State<Arc<AppState>>,
) -> Result<Json<Vec<Usuario>>, AppError> {
let usuarios = sqlx::query_as!(
Usuario,
"SELECT id, nome, email FROM usuarios ORDER BY id"
)
.fetch_all(&state.pool)
.await
.map_err(|e| AppError::ErroInterno(e.to_string()))?;
Ok(Json(usuarios))
}
Comparação: Axum vs Actix-Web vs Rocket vs Warp
| Característica | Axum | Actix-Web | Rocket | Warp |
|---|---|---|---|---|
| Runtime | Tokio | Tokio (via actix-rt) | Tokio | Tokio |
| Middleware | Tower (ecossistema) | Próprio | Fairings | Filters |
| Macros | Mínimas | Decoradores | Decoradores | Nenhuma |
| Performance | Excelente | Excelente | Boa | Muito boa |
| Curva de aprendizado | Moderada | Moderada | Fácil | Íngreme |
| Ecossistema | Tower + Tokio | Próprio | Próprio | Próprio |
| Popularidade 2026 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| Produção | AWS, Discord | Microsoft, Samsung | Projetos menores | Nicho |
O Axum lidera por combinar performance, ergonomia e o maior ecossistema de middleware disponível. Para um panorama completo do ecossistema, veja nosso artigo sobre o ecossistema Rust em 2026.
Padrões de Autenticação
Um padrão comum em APIs de produção é a autenticação via JWT:
use axum::{
extract::FromRequestParts,
http::request::Parts,
};
struct UsuarioAutenticado {
id: u64,
email: String,
}
#[axum::async_trait]
impl<S> FromRequestParts<S> for UsuarioAutenticado
where
S: Send + Sync,
{
type Rejection = AppError;
async fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> Result<Self, Self::Rejection> {
let header = parts
.headers
.get("Authorization")
.ok_or(AppError::NaoAutorizado)?;
// Validar JWT e extrair claims
// ...
Ok(UsuarioAutenticado { id: 1, email: "user@example.com".into() })
}
}
// O handler recebe o usuário autenticado automaticamente
async fn perfil(usuario: UsuarioAutenticado) -> Json<serde_json::Value> {
Json(serde_json::json!({
"id": usuario.id,
"email": usuario.email
}))
}
Conclusão
O Axum representou uma mudança de paradigma no desenvolvimento web com Rust. Em vez de competir reinventando abstrações, ele abraçou o ecossistema existente do Tokio e Tower, criando uma base sólida que a comunidade rapidamente adotou. Se você está começando com Rust para web ou migrando de outro framework, o Axum em 2026 é a escolha mais segura e produtiva.
Para programadores vindo de Go, a transição é natural — veja nosso guia Rust para Programadores Go para comparações detalhadas. E para entender como o async funciona por baixo dos panos, não deixe de ler sobre o ecossistema async do Rust.