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.

Redação CPFHub.io
Redação CPFHub.io
··8 min de leitura
Como implementar validação de CPF em Vue.js com 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 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:

// 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:

// 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:

// 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:

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:

// 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:

<!-- 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:

// 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:

// 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 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.


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 fornece os dados cadastrais necessários para completar o fluxo — comece com 50 consultas gratuitas por mês 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.

Redação CPFHub.io

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.

WhatsAppFale conosco via WhatsApp