# Como implementar validação de CPF em Vue.js com Composition API

> Tutorial completo de como implementar validação de CPF em Vue.js 3 usando Composition API, composables e integração com a API da CPFHub.io.

**Publicado:** 11/04/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-implementar-validacao-cpf-vuejs-composition-api

---


## Introdução

Vue.js 3 trouxe a Composition API como uma nova forma de organizar a lógica de componentes, oferecendo maior reutilização de código e melhor tipagem com TypeScript. Para desenvolvedores que trabalham com formulários de cadastro e checkout no Brasil, a validação de CPF é um requisito recorrente que se beneficia diretamente dessa arquitetura.

A validação de CPF envolve duas etapas: a verificação local dos dígitos verificadores (que pode ser feita sem requisição de rede) e a consulta a uma API externa para confirmar que o CPF realmente existe e obter dados do titular. A [**CPFHub.io**](https://www.cpfhub.io/) resolve a segunda etapa com uma chamada REST que retorna nome, gênero e data de nascimento do titular. Este tutorial mostra como implementar ambas no Vue.js 3 usando Composition API, com composables reutilizáveis, validação local e integração com a API da CPFHub.io.

---

## Arquitetura da solução

A implementação será organizada em três composables:

* **useCpfValidator** -- Validação local dos dígitos verificadores.

* **useCpfLookup** -- Consulta à API da CPFHub.io via backend proxy.

* **useCpfForm** -- Composable que combina os dois anteriores e gerencia o estado do formulário.

Essa separação permite reutilizar cada composable independentemente em diferentes partes da aplicação.

---

## Composable de validação local

O primeiro composable válida os dígitos verificadores do CPF sem fazer requisição de rede:

```typescript
// src/composables/useCpfValidator.ts
import { ref, computed } from 'vue';

export function useCpfValidator() {
 const cpf = ref('');

 const cpfLimpo = computed(() => cpf.value.replace(/\D/g, ''));

 const isFormatoValido = computed(() => {
 const valor = cpfLimpo.value;
 if (valor.length !== 11) return false;
 if (/^(\d)\1{10}$/.test(valor)) return false;

 // Primeiro dígito verificador
 let soma = 0;
 for (let i = 0; i < 9; i++) {
 soma += parseInt(valor.charAt(i)) * (10 - i);
 }
 let resto = (soma * 10) % 11;
 if (resto === 10) resto = 0;
 if (resto !== parseInt(valor.charAt(9))) return false;

 // Segundo dígito verificador
 soma = 0;
 for (let i = 0; i < 10; i++) {
 soma += parseInt(valor.charAt(i)) * (11 - i);
 }
 resto = (soma * 10) % 11;
 if (resto === 10) resto = 0;
 if (resto !== parseInt(valor.charAt(10))) return false;

 return true;
 });

 const cpfFormatado = computed(() => {
 const valor = cpfLimpo.value;
 if (valor.length !== 11) return valor;
 return `${valor.slice(0, 3)}.${valor.slice(3, 6)}.${valor.slice(6, 9)}-${valor.slice(9)}`;
 });

 return {
 cpf,
 cpfLimpo,
 isFormatoValido,
 cpfFormatado
 };
}
```

---

## Composable de consulta à API

O segundo composable encapsula a chamada à API. Por segurança, a requisição passa por um backend proxy que protege a chave de API:

```typescript
// src/composables/useCpfLookup.ts
import { ref } from 'vue';

interface CpfData {
 cpf: string;
 name: string;
 nameUpper: string;
 gender: string;
 birthDate: string;
 day: number;
 month: number;
 year: number;
}

interface CpfResponse {
 success: boolean;
 data: CpfData;
}

export function useCpfLookup() {
 const dados = ref<CpfData | null>(null);
 const carregando = ref(false);
 const erro = ref<string | null>(null);

 async function consultar(cpf: string): Promise<void> {
 const cpfLimpo = cpf.replace(/\D/g, '');
 carregando.value = true;
 erro.value = null;
 dados.value = null;

 try {
 const controller = new AbortController();
 const timeoutId = setTimeout(() => controller.abort(), 10000);

 const response = await fetch(`/api/cpf/${cpfLimpo}`, {
 method: 'GET',
 headers: { 'Accept': 'application/json' },
 signal: controller.signal
 });

 clearTimeout(timeoutId);

 if (!response.ok) {
 const mensagens: Record<number, string> = {
 400: 'CPF em formato inválido.',
 401: 'Erro de autenticação.',
 429: 'Muitas requisições. Tente novamente em instantes.',
 500: 'Erro no servidor. Tente novamente.'
 };
 throw new Error(
 mensagens[response.status] || `Erro HTTP ${response.status}`
 );
 }

 const resultado: CpfResponse = await response.json();

 if (resultado.success) {
 dados.value = resultado.data;
 } else {
 throw new Error('CPF não encontrado.');
 }
 } catch (e) {
 if (e instanceof Error) {
 erro.value = e.name === 'AbortError'
 ? 'Tempo de resposta excedido.'
 : e.message;
 }
 } finally {
 carregando.value = false;
 }
 }

 function limpar(): void {
 dados.value = null;
 erro.value = null;
 }

 return {
 dados,
 carregando,
 erro,
 consultar,
 limpar
 };
}
```

---

## Backend proxy para proteger a chave de API

A chave de API nunca deve ficar exposta no frontend. Crie um proxy simples no backend:

```javascript
// server/api/cpf/[cpf].js (Nuxt 3) ou endpoint Express
export default defineEventHandler(async (event) => {
 const cpf = getRouterParam(event, 'cpf');

 const response = await fetch(
 `https://api.cpfhub.io/cpf/${cpf}`,
 {
 method: 'GET',
 headers: {
 'x-api-key': process.env.CPFHUB_API_KEY,
 'Accept': 'application/json'
 }
 }
 );

 return response.json();
});
```

Ou com Express:

```javascript
const express = require('express');
const app = express();

app.get('/api/cpf/:cpf', async (req, res) => {
 const { cpf } = req.params;
 const response = await fetch(
 `https://api.cpfhub.io/cpf/${cpf}`,
 {
 headers: {
 'x-api-key': process.env.CPFHUB_API_KEY,
 'Accept': 'application/json'
 }
 }
 );
 const data = await response.json();
 res.json(data);
});

app.listen(3000);
```

---

## Composable combinado para o formulário

O terceiro composable combina validação local e consulta remota:

```typescript
// src/composables/useCpfForm.ts
import { watch } from 'vue';
import { useCpfValidator } from './useCpfValidator';
import { useCpfLookup } from './useCpfLookup';

export function useCpfForm() {
 const { cpf, cpfLimpo, isFormatoValido, cpfFormatado } = useCpfValidator();
 const { dados, carregando, erro, consultar, limpar } = useCpfLookup();

 // Limpar dados da API quando o CPF muda
 watch(cpf, () => {
 limpar();
 });

 async function validarEConsultar(): Promise<void> {
 if (!isFormatoValido.value) {
 return;
 }
 await consultar(cpfLimpo.value);
 }

 return {
 cpf,
 cpfFormatado,
 isFormatoValido,
 dados,
 carregando,
 erro,
 validarEConsultar
 };
}
```

---

## Componente Vue com o formulário

Com os composables prontos, o componente fica limpo e focado na apresentação:

```vue
<!-- src/components/CpfForm.vue -->
<template>
 <form @submit.prevent="validarEConsultar">
 <div class="campo">
 <label for="cpf">CPF</label>
 <input
 id="cpf"
 v-model="cpf"
 type="text"
 placeholder="000.000.000-00"
 maxlength="14"
 />
 <span v-if="cpf.length > 0 && !isFormatoValido" class="erro">
 CPF inválido.
 </span>
 </div>

 <button
 type="submit"
 :disabled="!isFormatoValido || carregando"
 >
 {{ carregando ? 'Consultando...' : 'Consultar CPF' }}
 </button>

 <div v-if="dados" class="resultado">
 <h3>Dados do titular</h3>
 <p><strong>Nome:</strong> {{ dados.name }}</p>
 <p>
 <strong>Genero:</strong>
 {{ dados.gender === 'M' ? 'Masculino' : 'Feminino' }}
 </p>
 <p><strong>Data de nascimento:</strong> {{ dados.birthDate }}</p>
 </div>

 <div v-if="erro" class="erro-consulta">
 <p>{{ erro }}</p>
 </div>
 </form>
</template>

<script setup lang="ts">
import { useCpfForm } from '../composables/useCpfForm';

const {
 cpf,
 isFormatoValido,
 dados,
 carregando,
 erro,
 validarEConsultar
} = useCpfForm();
</script>
```

---

## Adicionando máscara de input

Para melhorar a experiência do usuário, adicione formatação automática ao campo de CPF:

```typescript
// src/composables/useCpfMask.ts
import { ref, watch } from 'vue';

export function useCpfMask() {
 const valorBruto = ref('');
 const valorMascarado = ref('');

 watch(valorBruto, (novo) => {
 const numeros = novo.replace(/\D/g, '').slice(0, 11);
 let formatado = numeros;

 if (numeros.length > 9) {
 formatado = `${numeros.slice(0, 3)}.${numeros.slice(3, 6)}.${numeros.slice(6, 9)}-${numeros.slice(9)}`;
 } else if (numeros.length > 6) {
 formatado = `${numeros.slice(0, 3)}.${numeros.slice(3, 6)}.${numeros.slice(6)}`;
 } else if (numeros.length > 3) {
 formatado = `${numeros.slice(0, 3)}.${numeros.slice(3)}`;
 }

 valorMascarado.value = formatado;
 });

 return {
 valorBruto,
 valorMascarado
 };
}
```

---

## Testando os composables

A Composition API facilita testes unitários, pois os composables podem ser testados independentemente dos componentes:

```typescript
// src/composables/__tests__/useCpfValidator.test.ts
import { useCpfValidator } from '../useCpfValidator';

describe('useCpfValidator', () => {
 test('deve validar CPF com dígitos corretos', () => {
 const { cpf, isFormatoValido } = useCpfValidator();
 cpf.value = '52998224725';
 expect(isFormatoValido.value).toBe(true);
 });

 test('deve rejeitar CPF com dígitos repetidos', () => {
 const { cpf, isFormatoValido } = useCpfValidator();
 cpf.value = '11111111111';
 expect(isFormatoValido.value).toBe(false);
 });

 test('deve rejeitar CPF com tamanho incorreto', () => {
 const { cpf, isFormatoValido } = useCpfValidator();
 cpf.value = '123456';
 expect(isFormatoValido.value).toBe(false);
 });

 test('deve formatar CPF corretamente', () => {
 const { cpf, cpfFormatado } = useCpfValidator();
 cpf.value = '52998224725';
 expect(cpfFormatado.value).toBe('529.982.247-25');
 });
});
```

---

## Perguntas frequentes

### O que é necessário para implementar validação de CPF neste contexto?
A validação de CPF exige uma chamada à API com o número do documento e a chave de autenticação. A CPFHub.io retorna o status do CPF, nome do titular e data de nascimento em menos de 200ms, permitindo a verificação em tempo real durante o cadastro ou transação.

### A API CPFHub.io funciona para todos os volumes de consulta?
Sim. O plano gratuito oferece 50 consultas por mês sem cartão de crédito — ideal para testes e projetos pequenos. Para volumes maiores, o plano Pro inclui 1.000 consultas mensais por R$149. Se o limite for ultrapassado, a API não bloqueia: cobra R$0,15 por consulta adicional.

### Como garantir conformidade com a LGPD ao usar uma API de CPF?
Use o CPF apenas para a finalidade declarada ao titular, armazene apenas o necessário (não guarde o CPF cru se um token bastar), implemente controle de acesso aos logs de consulta e documente a base legal para o tratamento. A [ANPD](https://www.gov.br/anpd) orienta que dados de identificação devem ser tratados com o princípio da necessidade.

### Quanto tempo leva para integrar a API CPFHub.io?
A integração básica leva menos de 30 minutos: crie uma conta em cpfhub.io, gere a API key no painel e faça uma chamada GET para `https://api.cpfhub.io/cpf/{CPF}` com o header `x-api-key`. A documentação inclui exemplos em Python, Node.js, PHP, Java e outras linguagens.

### Leia também

- [Diferença entre validação de CPF e consulta de CPF: quando usar cada uma](https://cpfhub.io/blog/diferenca-entre-validacao-de-cpf-e-consulta-de-cpf-quando-usar-cada-uma)
- [API de CPF grátis para desenvolvedores: como começar em 5 minutos](https://cpfhub.io/blog/api-cpf-gratis-desenvolvedores-comecar-5-minutos)
- [Onboarding digital em fintechs: como validar CPF em menos de 30 segundos](https://cpfhub.io/blog/onboarding-digital-em-fintechs-como-validar-cpf-em-menos-de-30-segundos)
- [KYC no Brasil: quais setores são obrigados a validar CPF por lei](https://cpfhub.io/blog/kyc-no-brasil-quais-setores-sao-obrigados-a-validar-cpf-por-lei)

---

## Conclusão

A Composition API do Vue.js 3 é ideal para organizar a lógica de validação de CPF em composables reutilizáveis. Separando a validação local, a consulta à API e a lógica do formulário em composables distintos, o código fica limpo, testável e fácil de manter. A [**CPFHub.io**](https://www.cpfhub.io/) fornece os dados cadastrais necessários para completar o fluxo — comece com 50 consultas gratuitas por mês em [cpfhub.io](https://www.cpfhub.io/).

