# Como integrar a API de CPF em uma aplicação Rust com Actix Web

> Aprenda a integrar a API de consulta de CPF em uma aplicação web Rust usando Actix Web com endpoints REST.

**Publicado:** 28/10/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-integrar-api-cpf-rust-actix-web

---


Para integrar a API da CPFHub.io em uma aplicação Actix Web, crie um struct `CpfClient` que encapsula o `reqwest::Client` e a API key, registre-o como dado compartilhado com `web::Data`, e exponha um handler `GET /api/cpf/{cpf}` que faz a consulta e retorna o resultado serializado com `serde`. A latência é de ~900ms e o endpoint da CPFHub.io é `GET https://api.cpfhub.io/cpf/{CPF}` com o header `x-api-key`.

## Introdução

O Actix Web é um dos frameworks web mais rápidos disponíveis, construído sobre o ecossistema Rust. Sua arquitetura baseada em actors e seu sistema de tipos robusto o tornam ideal para construir APIs de alta performance que consomem serviços externos como a CPFHub.io.

---

## Configurando o projeto

Crie o projeto e adicione as dependências ao `Cargo.toml`:

```rust
// Cargo.toml
[package]
name = "cpf-actix-api"
version = "0.1.0"
edition = "2021"

[dependencies]
actix-web = "4"
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
dotenv = "0.15"
```

| Dependência | Finalidade |
|---|---|
| `actix-web` | Framework web para servir endpoints HTTP |
| `reqwest` | Cliente HTTP para consumir a API do CPFHub |
| `serde` | Serialização e desserialização de dados |
| `dotenv` | Carregamento de variáveis de ambiente |

---

## Definindo os modelos

Crie structs para representar a resposta da API externa e a resposta que sua aplicação retornará:

```rust
use serde::{Deserialize, Serialize};

#[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(Debug, Serialize)]
pub struct CpfResultado {
 pub valido: bool,
 pub cpf: String,
 pub nome: Option<String>,
 pub data_nascimento: Option<String>,
 pub genero: Option<String>,
 pub mensagem: String,
}
```

---

## Criando o serviço de consulta

Encapsule a lógica de comunicação com a API em um módulo dedicado:

```rust
use reqwest::header::{HeaderMap, HeaderValue};

pub struct CpfClient {
 http_client: reqwest::Client,
 api_key: String,
}

impl CpfClient {
 pub fn new(api_key: String) -> Self {
 let http_client = reqwest::Client::builder()
 .timeout(std::time::Duration::from_secs(10))
 .build()
 .expect("Falha ao criar cliente HTTP");

 Self { http_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(|_| "Chave de API inválida".to_string())?,
 );

 let url = format!("https://api.cpfhub.io/cpf/{}", cpf);

 let response = self.http_client
 .get(&url)
 .headers(headers)
 .send()
 .await
 .map_err(|e| format!("Erro na requisição: {}", e))?;

 if !response.status().is_success() {
 return Err(format!("API retornou status: {}", response.status()));
 }

 response.json::<CpfApiResponse>()
 .await
 .map_err(|e| format!("Erro ao desserializar: {}", e))
 }
}
```

---

## Configurando os handlers do Actix Web

Crie os handlers que processam as requisições HTTP e utilizam o serviço de consulta:

```rust
use actix_web::{get, web, HttpResponse, Responder};

#[get("/api/cpf/{cpf}")]
async fn consultar_cpf(
 path: web::Path<String>,
 cpf_client: web::Data<CpfClient>,
) -> impl Responder {
 let cpf = path.into_inner();
 let cpf_limpo: String = cpf.chars().filter(|c| c.is_ascii_digit()).collect();

 if cpf_limpo.len() != 11 {
 return HttpResponse::BadRequest().json(CpfResultado {
 valido: false,
 cpf: cpf_limpo,
 nome: None,
 data_nascimento: None,
 genero: None,
 mensagem: "CPF deve conter 11 dígitos".to_string(),
 });
 }

 match cpf_client.consultar(&cpf_limpo).await {
 Ok(dados) if dados.success => {
 HttpResponse::Ok().json(CpfResultado {
 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 com sucesso".to_string(),
 })
 }
 Ok(_) => HttpResponse::NotFound().json(CpfResultado {
 valido: false,
 cpf: cpf_limpo,
 nome: None,
 data_nascimento: None,
 genero: None,
 mensagem: "CPF não encontrado".to_string(),
 }),
 Err(e) => HttpResponse::BadGateway().json(serde_json::json!({
 "erro": e
 })),
 }
}
```

---

## Montando a aplicação principal

Configure o servidor Actix Web com o serviço compartilhado e os endpoints:

```rust
use actix_web::{App, HttpServer};
use std::env;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
 dotenv::dotenv().ok();

 let api_key = env::var("CPFHUB_API_KEY")
 .expect("CPFHUB_API_KEY deve estar definida");

 let cpf_client = web::Data::new(CpfClient::new(api_key));

 println!("Servidor iniciado em http://localhost:8080");

 HttpServer::new(move || {
 App::new()
 .app_data(cpf_client.clone())
 .service(consultar_cpf)
 })
 .bind("127.0.0.1:8080")?
 .run()
 .await
}
```

---

## Perguntas frequentes

### A CPFHub.io retorna HTTP 429 quando o volume de consultas é alto?
Não. A API da CPFHub.io nunca retorna HTTP 429 nem bloqueia requisições por excesso de volume. Ao superar a cota mensal do plano, cada consulta adicional é cobrada automaticamente a R$0,15 — o servidor Actix Web continua recebendo respostas normalmente, sem necessidade de lógica de retry para rate limit.

### Como compartilhar o `CpfClient` de forma segura entre múltiplos workers do Actix Web?
Use `web::Data<CpfClient>` para registrar o cliente como dado compartilhado no `App`. O Actix Web clona o `Arc` interno automaticamente para cada worker, garantindo que o pool de conexões do `reqwest::Client` seja reutilizado com eficiência. Consulte a [documentação do Actix Web](https://actix.rs/docs/application/) para detalhes sobre dados de aplicação compartilhados.

### Como configurar timeout adequado para não travar o servidor durante consultas lentas?
Defina o timeout no builder do `reqwest::Client` com `.timeout(Duration::from_secs(10))`. Como a CPFHub.io responde em ~900ms, 10 segundos é uma margem segura. No handler do Actix Web, erros de timeout resultam em `HttpResponse::BadGateway`, o que sinaliza ao cliente que o problema é downstream, não no seu servidor.

### Como estruturar testes de integração para o handler de consulta de CPF no Actix Web?
Use `actix_web::test::init_service` para inicializar um servidor de teste e `test::call_service` para enviar requisições. Injete um `CpfClient` com URL configurável por variável de ambiente para apontar para um mock ou para a API real em testes de integração. Isso mantém os testes unitários rápidos e os de integração isolados.

### Leia também

- [Como consumir a API de CPF em Rust usando reqwest](https://cpfhub.io/blog/como-consumir-api-cpf-rust-reqwest)
- [Como integrar a API de CPF em uma aplicação Rust com Axum](https://cpfhub.io/blog/como-integrar-api-cpf-rust-axum)
- [Como criar um CLI de validação de CPF em Rust](https://cpfhub.io/blog/como-criar-cli-validacao-cpf-rust)
- [Como processar consultas de CPF de forma assíncrona em Rust com Tokio](https://cpfhub.io/blog/como-processar-consultas-cpf-assincrono-rust-tokio)

---

## Conclusão

O Actix Web, combinado com `reqwest`, proporciona uma base sólida para construir APIs de consulta de CPF com altíssima performance em Rust. A segurança de tipos do Rust garante que erros sejam capturados em tempo de compilação, reduzindo bugs em produção. Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a construir sua integração Actix Web com a API de CPF hoje mesmo.

