Criar Servidor HTTP em Rust
O axum é um framework web moderno e ergonômico, construído sobre o ecossistema tokio e tower. Ele oferece rotas tipadas, extração automática de parâmetros e excelente performance.
Dependências
Cargo.toml:
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tower-http = { version = "0.5", features = ["cors"] }
Servidor básico com rotas
Um servidor mínimo com diferentes rotas:
use axum::{routing::get, Router};
async fn index() -> &'static str {
"Bem-vindo à API Rust!"
}
async fn saude() -> &'static str {
"OK"
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(index))
.route("/saude", get(saude));
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();
}
Para testar (em outro terminal):
$ curl http://localhost:3000
Bem-vindo à API Rust!
$ curl http://localhost:3000/saude
OK
Respostas JSON
Retorne dados estruturados como JSON:
use axum::{routing::get, Json, Router};
use serde::Serialize;
#[derive(Serialize)]
struct Produto {
id: u32,
nome: String,
preco: f64,
disponivel: bool,
}
#[derive(Serialize)]
struct ListaProdutos {
total: usize,
produtos: Vec<Produto>,
}
async fn listar_produtos() -> Json<ListaProdutos> {
let produtos = vec![
Produto {
id: 1,
nome: "Notebook".to_string(),
preco: 4599.90,
disponivel: true,
},
Produto {
id: 2,
nome: "Mouse".to_string(),
preco: 89.99,
disponivel: true,
},
Produto {
id: 3,
nome: "Monitor Ultrawide".to_string(),
preco: 3299.00,
disponivel: false,
},
];
let total = produtos.len();
Json(ListaProdutos { total, produtos })
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/api/produtos", get(listar_produtos));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("API rodando em http://localhost:3000");
axum::serve(listener, app).await.unwrap();
}
Saída (curl):
{
"total": 3,
"produtos": [
{"id": 1, "nome": "Notebook", "preco": 4599.90, "disponivel": true},
{"id": 2, "nome": "Mouse", "preco": 89.99, "disponivel": true},
{"id": 3, "nome": "Monitor Ultrawide", "preco": 3299.0, "disponivel": false}
]
}
Receber JSON no body (POST)
Aceite dados JSON nas requisições:
use axum::{http::StatusCode, routing::{get, post}, Json, Router};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct NovoProduto {
nome: String,
preco: f64,
}
#[derive(Serialize)]
struct ProdutoCriado {
id: u32,
nome: String,
preco: f64,
mensagem: String,
}
async fn criar_produto(
Json(novo): Json<NovoProduto>,
) -> (StatusCode, Json<ProdutoCriado>) {
// Em uma aplicação real, salvaria no banco de dados
let criado = ProdutoCriado {
id: 42, // ID gerado
nome: novo.nome,
preco: novo.preco,
mensagem: "Produto criado com sucesso!".to_string(),
};
(StatusCode::CREATED, Json(criado))
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/api/produtos", post(criar_produto));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("API rodando em http://localhost:3000");
axum::serve(listener, app).await.unwrap();
}
Para testar:
curl -X POST http://localhost:3000/api/produtos \
-H "Content-Type: application/json" \
-d '{"nome": "Teclado Mecânico", "preco": 349.90}'
Saída:
{
"id": 42,
"nome": "Teclado Mecânico",
"preco": 349.9,
"mensagem": "Produto criado com sucesso!"
}
Parâmetros de rota e query
Extraia parâmetros da URL e query string:
use axum::{
extract::{Path, Query},
routing::get,
Json, Router,
};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct FiltroQuery {
pagina: Option<u32>,
limite: Option<u32>,
busca: Option<String>,
}
#[derive(Serialize)]
struct Resposta {
mensagem: String,
}
// Parâmetro na URL: /usuarios/42
async fn buscar_usuario(Path(id): Path<u32>) -> Json<Resposta> {
Json(Resposta {
mensagem: format!("Usuário #{} encontrado", id),
})
}
// Query string: /buscar?pagina=1&limite=10&busca=rust
async fn buscar(Query(filtro): Query<FiltroQuery>) -> Json<serde_json::Value> {
let pagina = filtro.pagina.unwrap_or(1);
let limite = filtro.limite.unwrap_or(10);
let busca = filtro.busca.unwrap_or_default();
Json(serde_json::json!({
"pagina": pagina,
"limite": limite,
"busca": busca,
"mensagem": format!("Buscando '{}', página {}, {} por página", busca, pagina, limite)
}))
}
// Múltiplos parâmetros: /api/v1/categorias/eletronicos/produtos/42
async fn produto_por_categoria(
Path((categoria, produto_id)): Path<(String, u32)>,
) -> Json<Resposta> {
Json(Resposta {
mensagem: format!("Produto #{} na categoria '{}'", produto_id, categoria),
})
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/usuarios/{id}", get(buscar_usuario))
.route("/buscar", get(buscar))
.route("/categorias/{categoria}/produtos/{id}", get(produto_por_categoria));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("API rodando em http://localhost:3000");
axum::serve(listener, app).await.unwrap();
}
Saída (exemplos):
GET /usuarios/42
{"mensagem": "Usuário #42 encontrado"}
GET /buscar?pagina=2&limite=5&busca=notebook
{"pagina":2,"limite":5,"busca":"notebook","mensagem":"Buscando 'notebook', página 2, 5 por página"}
Estado compartilhado
Compartilhe dados entre handlers usando State:
use axum::{extract::State, routing::get, Json, Router};
use std::sync::{Arc, Mutex};
use serde::Serialize;
#[derive(Serialize, Clone)]
struct Estatisticas {
requisicoes: u64,
ultima_visita: String,
}
type AppState = Arc<Mutex<Estatisticas>>;
async fn estatisticas(State(estado): State<AppState>) -> Json<Estatisticas> {
let mut stats = estado.lock().unwrap();
stats.requisicoes += 1;
stats.ultima_visita = chrono::Utc::now().to_string();
Json(stats.clone())
}
#[tokio::main]
async fn main() {
let estado = Arc::new(Mutex::new(Estatisticas {
requisicoes: 0,
ultima_visita: String::new(),
}));
let app = Router::new()
.route("/stats", get(estatisticas))
.with_state(estado);
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();
}
Veja também
- Fazer Requisição HTTP — construa o lado cliente
- Parse JSON em Rust — processe JSON recebido no servidor
- Serializar Struct para JSON — prepare respostas JSON
- Async/Await Básico — entenda o modelo assíncrono usado pelo axum
- Conectar ao PostgreSQL — integre banco de dados no seu servidor
- Documentação da crate: axum