O Alpine.js permite consumir a API de CPF da CPFHub.io com reatividade declarativa diretamente no HTML, sem build steps e sem frameworks pesados. Com x-data gerenciando o estado e fetch fazendo a chamada ao backend proxy, a consulta acontece em tempo real enquanto o usuário digita — com latência de aproximadamente 900ms da API. A chave de API nunca fica exposta no navegador: o Alpine.js chama seu próprio servidor, que encaminha a requisição à CPFHub.io com o header x-api-key. Veja a documentação oficial do Alpine.js para referência das diretivas usadas neste guia.
1. Pré-requisitos
-
Um servidor web ou backend para servir HTML e funcionar como proxy da API (a chave de API não deve ficar no frontend).
-
Conhecimento básico de HTML e JavaScript.
-
Uma conta gratuita na CPFHub.io
2. Arquitetura da solução
Em aplicações frontend, a chave de API nunca deve ficar exposta no navegador. A arquitetura recomendada utiliza um backend proxy:
[Alpine.js Frontend] --fetch--> [Backend Proxy] --API Key--> [CPFHub.io API]
O backend recebe o CPF do frontend, adiciona a chave de API e encaminha a requisição para a CPFHub.io.
3. Crie o backend proxy
Um servidor simples em Node.js (Express) para proxy da API:
// server.js
const express = require("express");
const path = require("path");
const app = express();
const CPFHUB_API_KEY = process.env.CPFHUB_API_KEY || "SUA_CHAVE_DE_API";
const CPFHUB_BASE_URL = "https://api.cpfhub.io";
const TIMEOUT_MS = 5000;
app.use(express.static("public"));
app.use(express.json());
app.get("/api/cpf/:cpf", async (req, res) => {
const cpf = req.params.cpf.replace(/\D/g, "");
if (cpf.length !== 11) {
return res.status(400).json({
success: false,
error: "CPF deve conter exatamente 11 dígitos.",
});
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
try {
const response = await fetch(`${CPFHUB_BASE_URL}/cpf/${cpf}`, {
headers: {
"x-api-key": CPFHUB_API_KEY,
"Accept": "application/json",
},
signal: controller.signal,
});
clearTimeout(timeoutId);
const data = await response.json();
if (response.ok && data.success) {
return res.json({ success: true, data: data.data });
}
const errorMap = {
400: "CPF com formato inválido.",
401: "Erro de autenticação no servidor.",
404: "CPF não encontrado na base de dados.",
};
return res.status(response.status).json({
success: false,
error: errorMap[response.status] || `Erro HTTP ${response.status}`,
});
} catch (error) {
clearTimeout(timeoutId);
if (error.name === "AbortError") {
return res.status(504).json({
success: false,
error: "Timeout na consulta.",
});
}
return res.status(502).json({
success: false,
error: "Erro de conexão com a API.",
});
}
});
app.listen(3000, () => {
console.log("Servidor iniciado na porta 3000");
});
4. Crie a interface com Alpine.js
O componente Alpine.js com x-data gerencia todo o estado da consulta:
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Consulta de CPF - Alpine.js</title>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="container" x-data="cpfConsulta()">
<h1>Consulta de CPF</h1>
<p>Validacao de dados cadastrais via <strong>CPFHub.io</strong></p>
<!-- Formulário -->
<form @submit.prevent="consultar">
<div class="form-group">
<label for="cpf">CPF</label>
<input
type="text"
id="cpf"
x-model="cpfFormatado"
@input="aplicarMascara"
placeholder="000.000.000-00"
maxlength="14"
:disabled="loading"
/>
</div>
<button type="submit" :disabled="loading || cpfFormatado.length < 14">
<span x-show="!loading">Consultar CPF</span>
<span x-show="loading">Consultando...</span>
</button>
</form>
<!-- Indicador de loading -->
<div x-show="loading" x-transition class="loading">
Consultando a API da CPFHub.io...
</div>
<!-- Resultado de sucesso -->
<div x-show="resultado" x-transition class="card sucesso">
<h3>CPF Encontrado</h3>
<table>
<tr>
<td><strong>Nome</strong></td>
<td x-text="resultado?.name"></td>
</tr>
<tr>
<td><strong>CPF</strong></td>
<td x-text="resultado?.cpf"></td>
</tr>
<tr>
<td><strong>Genero</strong></td>
<td x-text="resultado?.gender === 'M' ? 'Masculino' : 'Feminino'"></td>
</tr>
<tr>
<td><strong>Nascimento</strong></td>
<td x-text="resultado?.birthDate"></td>
</tr>
<tr>
<td><strong>Idade</strong></td>
<td x-text="calcularIdade(resultado?.year)"></td>
</tr>
</table>
</div>
<!-- Mensagem de erro -->
<div x-show="erro" x-transition class="card erro">
<p x-text="erro"></p>
</div>
</div>
<script>
function cpfConsulta() {
return {
cpfFormatado: "",
resultado: null,
erro: null,
loading: false,
aplicarMascara() {
let numeros = this.cpfFormatado.replace(/\D/g, "").slice(0, 11);
if (numeros.length > 9) {
this.cpfFormatado = `${numeros.slice(0, 3)}.${numeros.slice(3, 6)}.${numeros.slice(6, 9)}-${numeros.slice(9)}`;
} else if (numeros.length > 6) {
this.cpfFormatado = `${numeros.slice(0, 3)}.${numeros.slice(3, 6)}.${numeros.slice(6)}`;
} else if (numeros.length > 3) {
this.cpfFormatado = `${numeros.slice(0, 3)}.${numeros.slice(3)}`;
} else {
this.cpfFormatado = numeros;
}
},
async consultar() {
const cpf = this.cpfFormatado.replace(/\D/g, "");
if (cpf.length !== 11) {
this.erro = "CPF deve conter 11 digitos.";
this.resultado = null;
return;
}
this.loading = true;
this.resultado = null;
this.erro = null;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(`/api/cpf/${cpf}`, {
headers: { "Accept": "application/json" },
signal: controller.signal,
});
clearTimeout(timeoutId);
const data = await response.json();
if (data.success) {
this.resultado = data.data;
} else {
this.erro = data.error || "Erro na consulta.";
}
} catch (error) {
clearTimeout(timeoutId);
if (error.name === "AbortError") {
this.erro = "Timeout na consulta. Tente novamente.";
} else {
this.erro = "Erro de conexao. Verifique sua internet.";
}
} finally {
this.loading = false;
}
},
calcularIdade(anoNascimento) {
if (!anoNascimento) return "";
const idade = new Date().getFullYear() - anoNascimento;
return `${idade} anos`;
},
};
}
</script>
</body>
</html>
5. Adicione estilos CSS
/* public/style.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #f5f5f5;
color: #333;
line-height: 1.6;
}
.container {
max-width: 560px;
margin: 40px auto;
padding: 0 20px;
}
h1 { margin-bottom: 4px; }
p { margin-bottom: 24px; color: #666; }
.form-group { margin-bottom: 16px; }
label {
display: block;
margin-bottom: 4px;
font-weight: 600;
}
input[type="text"] {
width: 100%;
padding: 12px;
font-size: 18px;
border: 2px solid #ddd;
border-radius: 6px;
transition: border-color 0.2s;
}
input:focus { outline: none; border-color: #3498db; }
input:disabled { background: #eee; cursor: not-allowed; }
button {
width: 100%;
padding: 14px;
font-size: 16px;
font-weight: 600;
background: #3498db;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s;
}
button:hover:not(:disabled) { background: #2980b9; }
button:disabled { opacity: 0.6; cursor: not-allowed; }
.loading {
margin-top: 16px;
padding: 12px;
text-align: center;
color: #888;
font-style: italic;
}
.card {
margin-top: 24px;
padding: 20px;
border-radius: 8px;
border: 1px solid #ddd;
}
.card.sucesso { border-color: #27ae60; background: #f0fff4; }
.card.erro { border-color: #e74c3c; background: #fff5f5; color: #c0392b; }
table { width: 100%; border-collapse: collapse; margin-top: 12px; }
td { padding: 8px 0; border-bottom: 1px solid #eee; }
td:first-child { width: 120px; color: #555; }
6. Adicione consulta com debounce
Para consultar automaticamente enquanto o usuário digita, com debounce:
<!-- Adicione ao input -->
<input
type="text"
x-model="cpfFormatado"
@input="aplicarMascara"
@input.debounce.500ms="consultarSeCompleto"
placeholder="000.000.000-00"
maxlength="14"
/>
// Adicione ao objeto x-data
consultarSeCompleto() {
const cpf = this.cpfFormatado.replace(/\D/g, "");
if (cpf.length === 11) {
this.consultar();
}
},
7. Adicione histórico de consultas
Use x-data para manter um histórico local das consultas:
<!-- Histórico de consultas -->
<div x-show="historico.length > 0" class="historico">
<h3>Historico de Consultas</h3>
<template x-for="item in historico" :key="item.cpf">
<div class="historico-item" @click="preencherCpf(item.cpf)">
<span x-text="item.nome"></span>
<small x-text="item.cpf"></small>
</div>
</template>
<button @click="limparHistorico" class="btn-limpar">Limpar Historico</button>
</div>
// Adicione ao objeto x-data
historico: JSON.parse(localStorage.getItem("cpf_historico") || "[]"),
salvarNoHistorico(dados) {
const item = { cpf: dados.cpf, nome: dados.name, data: new Date().toISOString() };
this.historico = [item, ...this.historico.filter(h => h.cpf !== dados.cpf)].slice(0, 10);
localStorage.setItem("cpf_historico", JSON.stringify(this.historico));
},
preencherCpf(cpf) {
const numeros = cpf.replace(/\D/g, "");
this.cpfFormatado = `${numeros.slice(0, 3)}.${numeros.slice(3, 6)}.${numeros.slice(6, 9)}-${numeros.slice(9)}`;
this.consultar();
},
limparHistorico() {
this.historico = [];
localStorage.removeItem("cpf_historico");
},
8. Boas práticas
-
Backend proxy -- Nunca exponha a chave de API no frontend. O Alpine.js faz requisições ao seu backend, que adiciona a chave e consulta a CPFHub.io.
-
Timeout -- Use
AbortControllercom timeout de 5 segundos para evitar que a interface trave em conexões lentas. -
Debounce -- Use
@input.debounce.500mspara evitar consultas excessivas enquanto o usuário digita. -
Transições -- Use
x-transitionpara animar a exibição de resultados e erros, melhorando a experiência do usuário. -
LocalStorage -- Para o histórico, use
localStoragecom cuidado. Dados de CPF são sensíveis e devem ser tratados conforme a LGPD. -
LGPD -- A API da CPFHub.io é 100% compatível com a LGPD. No frontend, não armazene dados pessoais sem consentimento explícito do usuário.
Perguntas frequentes
Por que usar um backend proxy ao integrar Alpine.js com a API de CPF?
O Alpine.js roda no navegador, o que significa que qualquer dado incluído no código JavaScript fica visível para o usuário. Colocar a chave de API diretamente no frontend a expõe a qualquer pessoa que inspecione o código-fonte. O backend proxy recebe o CPF, adiciona a chave de forma segura e repassa a requisição à CPFHub.io — protegendo suas credenciais e limitando o acesso ao endpoint.
A API da CPFHub.io bloqueia requisições quando o limite do plano gratuito é atingido?
Não. Ao atingir o limite de 50 consultas mensais do plano gratuito, a API continua respondendo e cobra R$0,15 por consulta adicional, sem bloquear nem retornar erro de cota. Para volumes previsíveis e maiores, o plano Pro oferece 1.000 consultas por R$149/mês com o mesmo excedente de R$0,15/consulta.
Como o Alpine.js lida com a latência de ~900ms da API de CPF?
O Alpine.js permite gerenciar o estado de loading com x-show="loading" e x-transition, exibindo uma mensagem de "consultando" enquanto a resposta chega. O debounce de 500ms no input evita disparar múltiplas requisições durante a digitação, e o AbortController cancela a chamada caso o usuário desista antes da resposta.
O histórico de consultas em localStorage é seguro para dados de CPF?
O localStorage persiste dados no navegador do usuário e pode ser lido por qualquer script da mesma origem. Para conformidade com a LGPD, armazene apenas o mínimo necessário (por exemplo, apenas os últimos 4 dígitos do CPF para identificação visual), obtenha consentimento explícito do usuário e disponibilize uma opção clara para limpar o histórico.
Conclusão
Consumir 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.



