Para integrar a API de CPF da CPFHub.io em uma aplicação Rust com Axum, basta criar um AppState com um cliente reqwest configurado, injetar a API key via variável de ambiente e chamar GET https://api.cpfhub.io/cpf/{CPF} com o header x-api-key. O Axum recebe o resultado nos handlers usando o extractor State, o que mantém o código idiomático e seguro. A latência típica é de ~900ms e o plano gratuito oferece 50 consultas mensais sem cartão de crédito.
Introdução
O Axum é um framework web moderno para Rust, desenvolvido pela equipe do Tokio. Ele se destaca pela integração nativa com o ecossistema Tokio, uso de extractors para parsing de requisições e compatibilidade com a tower middleware stack. A documentação oficial do Axum está disponível em docs.rs/axum.
Configurando o projeto
Crie o projeto e adicione as dependências necessárias:
// Cargo.toml
[package]
name = "cpf-axum-api"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tower-http = { version = "0.5", features = ["cors", "trace"] }
tracing = "0.1"
tracing-subscriber = "0.3"
| Dependência | Papel na aplicação |
|---|---|
axum | Framework web com roteamento e extractors |
tower-http | Middlewares de CORS e tracing |
tracing | Logging estruturado |
reqwest | Cliente HTTP para a API do CPFHub |
Definindo o estado da aplicação
No Axum, o estado compartilhado é passado para os handlers através de extractors tipados:
use std::sync::Arc;
use reqwest::header::{HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize};
#[derive(Clone)]
pub struct AppState {
pub cpf_client: Arc<CpfClient>,
}
pub struct CpfClient {
client: reqwest::Client,
api_key: String,
}
#[derive(Debug, Deserialize)]
pub struct CpfApiResponse {
pub success: bool,
pub data: CpfApiData,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CpfApiData {
pub cpf: String,
pub name: String,
pub name_upper: String,
pub gender: String,
pub birth_date: String,
pub day: String,
pub month: String,
pub year: String,
}
#[derive(Serialize)]
pub struct CpfResult {
pub valido: bool,
pub cpf: String,
pub nome: Option<String>,
pub data_nascimento: Option<String>,
pub genero: Option<String>,
pub mensagem: String,
}
impl CpfClient {
pub fn new(api_key: String) -> Self {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()
.expect("Falha ao criar cliente HTTP");
Self { client, api_key }
}
pub async fn consultar(&self, cpf: &str) -> Result<CpfApiResponse, String> {
let mut headers = HeaderMap::new();
headers.insert(
"x-api-key",
HeaderValue::from_str(&self.api_key).map_err(|e| e.to_string())?,
);
let url = format!("https://api.cpfhub.io/cpf/{}", cpf);
let response = self.client
.get(&url)
.headers(headers)
.send()
.await
.map_err(|e| format!("Erro na requisição: {}", e))?;
if !response.status().is_success() {
return Err(format!("Status: {}", response.status()));
}
response.json::<CpfApiResponse>()
.await
.map_err(|e| format!("Erro JSON: {}", e))
}
}
Criando os handlers
Os handlers no Axum utilizam extractors para receber parâmetros e estado:
use axum::{
extract::{Path, State},
http::StatusCode,
response::IntoResponse,
Json,
};
async fn consultar_cpf(
State(state): State<AppState>,
Path(cpf): Path<String>,
) -> impl IntoResponse {
let cpf_limpo: String = cpf.chars().filter(|c| c.is_ascii_digit()).collect();
if cpf_limpo.len() != 11 {
return (
StatusCode::BAD_REQUEST,
Json(CpfResult {
valido: false,
cpf: cpf_limpo,
nome: None,
data_nascimento: None,
genero: None,
mensagem: "CPF deve conter 11 dígitos".to_string(),
}),
);
}
match state.cpf_client.consultar(&cpf_limpo).await {
Ok(dados) if dados.success => (
StatusCode::OK,
Json(CpfResult {
valido: true,
cpf: dados.data.cpf,
nome: Some(dados.data.name),
data_nascimento: Some(dados.data.birth_date),
genero: Some(dados.data.gender),
mensagem: "CPF encontrado".to_string(),
}),
),
Ok(_) => (
StatusCode::NOT_FOUND,
Json(CpfResult {
valido: false,
cpf: cpf_limpo,
nome: None,
data_nascimento: None,
genero: None,
mensagem: "CPF não encontrado".to_string(),
}),
),
Err(e) => (
StatusCode::BAD_GATEWAY,
Json(CpfResult {
valido: false,
cpf: cpf_limpo,
nome: None,
data_nascimento: None,
genero: None,
mensagem: format!("Erro: {}", e),
}),
),
}
}
async fn health_check() -> impl IntoResponse {
Json(serde_json::json!({ "status": "ok" }))
}
Configurando rotas e middlewares
Monte a aplicação Axum com roteamento, CORS e logging:
use axum::Router;
use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;
#[tokio::main]
async fn main() {
tracing_subscriber::init();
let api_key = std::env::var("CPFHUB_API_KEY")
.expect("CPFHUB_API_KEY deve estar definida");
let state = AppState {
cpf_client: Arc::new(CpfClient::new(api_key)),
};
let app = Router::new()
.route("/api/cpf/:cpf", axum::routing::get(consultar_cpf))
.route("/health", axum::routing::get(health_check))
.layer(CorsLayer::permissive())
.layer(TraceLayer::new_for_http())
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
tracing::info!("Servidor iniciado em http://localhost:3000");
axum::serve(listener, app).await.unwrap();
}
Perguntas frequentes
Como o Axum lida com o estado compartilhado para chamadas à API de CPF?
O Axum injeta o estado via o extractor State<AppState> nos handlers. O CpfClient é encapsulado em um Arc para permitir clonagem sem custo entre threads. Essa abordagem garante que um único cliente reqwest — com pool de conexões — seja reutilizado em todas as requisições, o que melhora a performance e evita overhead de inicialização.
A API da CPFHub.io retorna erro 429 quando o limite de consultas é atingido?
Não. A API da CPFHub.io nunca bloqueia nem retorna HTTP 429 ao atingir o limite do plano. Quando o limite mensal é ultrapassado, cada consulta adicional é cobrada a R$0,15 automaticamente. Isso garante que sua aplicação Rust continue funcionando sem interrupções, independentemente do volume de requisições.
Como armazenar a API key com segurança em uma aplicação Rust com Axum?
A prática recomendada é injetar a chave via variável de ambiente (std::env::var("CPFHUB_API_KEY")), nunca hardcoded no código-fonte. Em produção, utilize um secret manager como AWS Secrets Manager ou HashiCorp Vault. O OWASP Cheat Sheet sobre segurança de APIs documenta boas práticas para gestão de credenciais em serviços web.
Como testar o handler de consulta de CPF com Axum sem chamar a API real?
Crie um trait CpfClientTrait e implemente um mock para testes. O Axum facilita isso pois os handlers aceitam qualquer tipo que implemente Clone. Use tower::ServiceExt e axum::body::to_bytes para simular requisições HTTP nos testes de integração sem subir um servidor real.
Conclusão
O Axum oferece uma abordagem moderna e ergonômica para construir APIs em Rust. Seus extractors tipados, integração com Tokio e compatibilidade com o ecossistema tower tornam a integração com APIs externas como a CPFHub.io limpa e eficiente.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a validar CPFs na sua aplicação Rust com Axum em menos de 30 minutos.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



