O Tauri é um framework para construção de aplicações desktop que combina um backend em Rust com um frontend web (HTML, CSS, JavaScript). Para integrar validação de CPF, a chamada à API da CPFHub.io é feita pelo backend Rust — mantendo a chave de API fora do alcance do webview — e o resultado é devolvido ao frontend via comando Tauri. A API responde em ~900ms com nome, data de nascimento e demais dados do titular ao receber GET https://api.cpfhub.io/cpf/{CPF} com o header x-api-key.
1. Pré-requisitos
-
Rust (rustup) e Node.js 18+ instalados.
-
Tauri CLI:
cargo install tauri-cli. -
Um projeto Tauri criado:
cargo tauri init. -
Uma conta gratuita na CPFHub.io
2. Adicione as dependências Rust
No Cargo.toml do projeto (dentro de src-tauri/), adicione as dependências necessárias:
[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
dotenv = "0.15"
3. Configure as variáveis de ambiente
Crie um arquivo .env na pasta src-tauri/:
CPFHUB_API_KEY=SUA_CHAVE_DE_API
CPFHUB_BASE_URL=https://api.cpfhub.io
CPFHUB_TIMEOUT=5
4. Crie as structs de resposta
Defina as structs Rust para deserializar a resposta da API:
// src-tauri/src/cpfhub.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct CpfResponse {
pub success: bool,
pub data: Option<CpfData>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CpfData {
pub cpf: String,
pub name: String,
#[serde(rename = "nameUpper")]
pub name_upper: String,
pub gender: String,
#[serde(rename = "birthDate")]
pub birth_date: String,
pub day: u32,
pub month: u32,
pub year: u32,
}
#[derive(Debug, Serialize)]
pub struct CpfError {
pub message: String,
pub status_code: u16,
}
5. Implemente o comando Tauri
Crie o comando que será invocado pelo frontend. Consulte a documentação do Tauri para referência completa sobre comandos:
// src-tauri/src/cpfhub.rs (continuação)
use reqwest::Client;
use std::time::Duration;
use std::env;
pub async fn consultar_cpf(cpf: &str) -> Result<CpfData, CpfError> {
let cpf_limpo: String = cpf.chars().filter(|c| c.is_ascii_digit()).collect();
if cpf_limpo.len() != 11 {
return Err(CpfError {
message: "CPF deve conter exatamente 11 dígitos".to_string(),
status_code: 400,
});
}
let api_key = env::var("CPFHUB_API_KEY").map_err(|_| CpfError {
message: "Chave de API não configurada".to_string(),
status_code: 500,
})?;
let base_url = env::var("CPFHUB_BASE_URL")
.unwrap_or_else(|_| "https://api.cpfhub.io".to_string());
let timeout: u64 = env::var("CPFHUB_TIMEOUT")
.unwrap_or_else(|_| "5".to_string())
.parse()
.unwrap_or(5);
let url = format!("{}/cpf/{}", base_url, cpf_limpo);
let client = Client::builder()
.timeout(Duration::from_secs(timeout))
.build()
.map_err(|e| CpfError {
message: format!("Erro ao criar cliente HTTP: {}", e),
status_code: 500,
})?;
let response = client
.get(&url)
.header("x-api-key", &api_key)
.header("Accept", "application/json")
.send()
.await
.map_err(|e| {
if e.is_timeout() {
CpfError {
message: "Timeout ao consultar a API".to_string(),
status_code: 504,
}
} else {
CpfError {
message: format!("Erro de conexão: {}", e),
status_code: 502,
}
}
})?;
let status = response.status().as_u16();
match status {
200 => {
let body: CpfResponse = response.json().await.map_err(|e| CpfError {
message: format!("Erro ao parsear resposta: {}", e),
status_code: 500,
})?;
if body.success {
body.data.ok_or(CpfError {
message: "Resposta sem dados".to_string(),
status_code: 500,
})
} else {
Err(CpfError {
message: "Consulta retornou sucesso falso".to_string(),
status_code: 500,
})
}
}
400 => Err(CpfError { message: "CPF com formato inválido".to_string(), status_code: 400 }),
401 => Err(CpfError { message: "Chave de API inválida".to_string(), status_code: 401 }),
404 => Err(CpfError { message: "CPF não encontrado".to_string(), status_code: 404 }),
_ => Err(CpfError { message: format!("Erro HTTP {}", status), status_code: status }),
}
}
6. Registre o comando no main.rs
Registre o comando Tauri para que o frontend possa invocá-lo:
// src-tauri/src/main.rs
mod cpfhub;
use cpfhub::{CpfData, CpfError};
#[tauri::command]
async fn consultar_cpf_command(cpf: String) -> Result<CpfData, String> {
dotenv::dotenv().ok();
match cpfhub::consultar_cpf(&cpf).await {
Ok(data) => Ok(data),
Err(e) => Err(format!("{}: {}", e.status_code, e.message)),
}
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![consultar_cpf_command])
.run(tauri::generate_context!())
.expect("Erro ao iniciar aplicação Tauri");
}
7. Crie o frontend
No frontend, invoque o comando Rust via @tauri-apps/api:
<!-- src/index.html -->
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Consulta de CPF - Tauri</title>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; }
input { padding: 10px; width: 200px; font-size: 16px; }
button { padding: 10px 20px; font-size: 16px; cursor: pointer; }
.resultado { margin-top: 20px; padding: 15px; border: 1px solid #ddd; border-radius: 4px; }
.erro { color: #c0392b; }
.sucesso { color: #27ae60; }
</style>
</head>
<body>
<h1>Consulta de CPF</h1>
<div>
<input type="text" id="cpf-input" placeholder="000.000.000-00" maxlength="14" />
<button id="btn-consultar">Consultar</button>
</div>
<div id="resultado" class="resultado" style="display: none;"></div>
<script type="module" src="/main.js"></script>
</body>
</html>
// src/main.js
const { invoke } = window.__TAURI__.core;
const btnConsultar = document.getElementById("btn-consultar");
const cpfInput = document.getElementById("cpf-input");
const resultadoDiv = document.getElementById("resultado");
btnConsultar.addEventListener("click", async () => {
const cpf = cpfInput.value.trim();
if (!cpf) {
mostrarErro("Digite um CPF.");
return;
}
resultadoDiv.style.display = "block";
resultadoDiv.innerHTML = "Consultando...";
resultadoDiv.className = "resultado";
try {
const dados = await invoke("consultar_cpf_command", { cpf });
mostrarSucesso(dados);
} catch (erro) {
mostrarErro(erro);
}
});
function mostrarSucesso(dados) {
resultadoDiv.className = "resultado sucesso";
resultadoDiv.innerHTML = `
<h3>CPF Encontrado</h3>
<p><strong>Nome:</strong> ${dados.name}</p>
<p><strong>CPF:</strong> ${dados.cpf}</p>
<p><strong>Genero:</strong> ${dados.gender}</p>
<p><strong>Nascimento:</strong> ${dados.birth_date}</p>
`;
}
function mostrarErro(mensagem) {
resultadoDiv.className = "resultado erro";
resultadoDiv.innerHTML = `<p>${mensagem}</p>`;
resultadoDiv.style.display = "block";
}
8. Boas práticas
-
Segurança -- A chave de API fica exclusivamente no backend Rust, inacessível ao frontend. Nunca exponha credenciais no webview.
-
Timeout -- Configure timeout no
reqwest::Clientpara evitar que a aplicação trave aguardando respostas da rede. -
Variáveis de ambiente -- Use
.envpara desenvolvimento e variáveis de ambiente do sistema para distribuição. -
Tratamento de erros -- Mapeie cada código HTTP para uma mensagem clara que será exibida ao usuário.
-
Performance -- O Tauri com Rust no backend oferece performance nativa. A API da CPFHub responde em ~900ms, proporcionando uma experiência fluida ao usuário.
-
LGPD -- A API da CPFHub.io é 100% compatível com a LGPD. Em aplicações desktop, garanta que dados pessoais não sejam armazenados localmente sem o consentimento do usuário.
Perguntas frequentes
Por que fazer a chamada à API de CPF pelo backend Rust no Tauri?
O backend Rust em uma aplicação Tauri roda em processo separado do webview, sem acesso via DevTools do navegador. Isso significa que a chave de API nunca fica exposta ao ambiente web — qualquer inspeção de tráfego ou acesso ao webview não revela a credencial. A prática segue as recomendações de segurança da OWASP para proteção de secrets em aplicações cliente.
A API da CPFHub.io bloqueia quando o limite do plano é atingido?
Não. Ao atingir o limite mensal, a API continua respondendo normalmente e cobra R$0,15 por consulta adicional — sem retornar erro nem bloquear requisições. O plano gratuito inclui 50 consultas/mês e o plano Pro inclui 1.000 consultas/mês por R$149. O controle de uso fica disponível no painel em app.cpfhub.io/settings/billing.
Como tratar o tempo de resposta de ~900ms em uma aplicação desktop Tauri?
Mostre um estado de carregamento no frontend assim que o usuário acionar a consulta, antes de aguardar a resposta do comando Rust. No frontend JavaScript, defina resultadoDiv.innerHTML = "Consultando..." antes do invoke, e use try/catch para exibir mensagem de erro em caso de timeout. Configure o reqwest::Client com timeout adequado para evitar que a UI trave indefinidamente.
Como distribuir uma aplicação Tauri com a chave de API protegida?
Para distribuição, use variáveis de ambiente do sistema operacional em vez de arquivo .env. O instalador pode solicitar a chave durante a configuração inicial e armazená-la nas variáveis de ambiente do usuário. Outra abordagem é usar um backend intermediário (seu próprio servidor) que armazena a chave e serve como proxy para a API da CPFHub.io, removendo completamente a credencial do pacote distribuído.
Conclusão
Integrar a API da CPFHub.io
Cadastre-se em cpfhub.io
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.



