Como validar CPF no servidor usando Next.js API Routes

Aprenda a validar CPF no servidor usando Next.js API Routes com validação algorítmica e consulta à API do CPFHub.

Redação CPFHub.io
Redação CPFHub.io
··7 min de leitura
Como validar CPF no servidor usando Next.js API Routes

Validar CPF no servidor com Next.js API Routes garante que somente documentos algoritmicamente corretos e presentes na base da Receita Federal sejam aceitos pelo sistema. A validação no servidor — e não apenas no cliente — protege contra manipulação de requisições e evita chamadas desnecessárias à API externa. O fluxo combina validação local de dígitos verificadores com consulta ao endpoint GET https://api.cpfhub.io/cpf/{CPF} usando o header x-api-key.

Introdução

Validar CPF exclusivamente no cliente é insuficiente para garantir a integridade dos dados. A validação no servidor protege contra manipulação de requisições e garante que somente CPFs verificados sejam aceitos pelo sistema. Com o Next.js App Router, é possível criar Route Handlers que validam o CPF algoritmicamente e consultam a API do CPFHub.io para confirmação dos dados cadastrais.


Criando o módulo de validação

Implemente a validação algorítmica do CPF em um módulo reutilizável:

// lib/cpf-validator.js

export function validarDigitosCpf(cpf) {
    const cpfLimpo = cpf.replace(/\D/g, '');

    if (cpfLimpo.length !== 11) {
    return { valido: false, erro: 'CPF deve conter 11 dígitos' };
    }

    if (/^(\d)\1{10}$/.test(cpfLimpo)) {
    return { valido: false, erro: 'CPF com todos os dígitos iguais' };
    }

    const digitos = cpfLimpo.split('').map(Number);

    let soma = 0;
    for (let i = 0; i < 9; i++) {
    soma += digitos[i] * (10 - i);
    }
    const d1 = soma % 11 < 2 ? 0 : 11 - (soma % 11);

    soma = 0;
    for (let i = 0; i < 10; i++) {
    soma += digitos[i] * (11 - i);
    }
    const d2 = soma % 11 < 2 ? 0 : 11 - (soma % 11);

    if (digitos[9] !== d1 || digitos[10] !== d2) {
    return { valido: false, erro: 'Dígitos verificadores inválidos' };
    }

    return { valido: true, cpfLimpo };
}

export function formatarCpf(cpf) {
    const limpo = cpf.replace(/\D/g, '');
    return limpo.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');
}

Route Handler de validação

Crie o Route Handler que combina validação local com consulta à API:

// app/api/validar-cpf/route.js

import { validarDigitosCpf } from '@/lib/cpf-validator';

export async function POST(request) {
    const body = await request.json();
    const { cpf } = body;

    if (!cpf) {
    return Response.json(
    { success: false, message: 'CPF é obrigatório' },
    { status: 400 }
    );
    }

    const validacaoLocal = validarDigitosCpf(cpf);

    if (!validacaoLocal.valido) {
    return Response.json({
    success: false,
    validacaoLocal: false,
    message: validacaoLocal.erro,
    }, { status: 400 });
    }

    try {
    const apiResponse = await fetch(
    `${process.env.CPFHUB_BASE_URL}/cpf/${validacaoLocal.cpfLimpo}`,
    {
    headers: {
    'x-api-key': process.env.CPFHUB_API_KEY,
    },
    }
    );

    if (!apiResponse.ok) {
    return Response.json({
    success: true,
    validacaoLocal: true,
    encontradoNaBase: false,
    message: 'CPF válido, porém não encontrado na base',
    });
    }

    const dados = await apiResponse.json();

    return Response.json({
    success: true,
    validacaoLocal: true,
    encontradoNaBase: dados.success,
    data: dados.success ? dados.data : null,
    message: dados.success
    ? 'CPF válido e encontrado'
    : 'CPF válido, mas sem dados',
    });
    } catch (error) {
    return Response.json({
    success: false,
    validacaoLocal: true,
    message: 'Erro ao consultar API externa',
    }, { status: 502 });
    }
}

Route Handler para validação em lote

Crie um endpoint que aceita múltiplos CPFs para validação simultânea:

// app/api/validar-cpf/lote/route.js

import { validarDigitosCpf } from '@/lib/cpf-validator';

export async function POST(request) {
    const { cpfs } = await request.json();

    if (!Array.isArray(cpfs) || cpfs.length === 0) {
    return Response.json(
    { success: false, message: 'Envie um array de CPFs' },
    { status: 400 }
    );
    }

    if (cpfs.length > 50) {
    return Response.json(
    { success: false, message: 'Máximo de 50 CPFs por requisição' },
    { status: 400 }
    );
    }

    const resultados = await Promise.allSettled(
    cpfs.map(async (cpf) => {
    const validacao = validarDigitosCpf(cpf);

    if (!validacao.valido) {
    return { cpf, valido: false, erro: validacao.erro };
    }

    const response = await fetch(
    `${process.env.CPFHUB_BASE_URL}/cpf/${validacao.cpfLimpo}`,
    {
    headers: { 'x-api-key': process.env.CPFHUB_API_KEY },
    }
    );

    if (!response.ok) {
    return { cpf, valido: true, encontrado: false };
    }

    const dados = await response.json();
    return {
    cpf,
    valido: true,
    encontrado: dados.success,
    nome: dados.success ? dados.data.name : null,
    };
    })
    );

    const processados = resultados.map((r) =>
    r.status === 'fulfilled' ? r.value : { erro: 'Falha no processamento' }
    );

    return Response.json({ success: true, resultados: processados });
}
EndpointMétodoCorpoDescrição
/api/validar-cpfPOST{ "cpf": "123..." }Validação individual
/api/validar-cpf/lotePOST{ "cpfs": [...] }Validação em lote (max 50)

Middleware de rate limiting

Proteja os endpoints contra abuso implementando rate limiting:

// middleware.js

import { NextResponse } from 'next/server';

const rateLimit = new Map();
const WINDOW_MS = 60 * 1000;
const MAX_REQUESTS = 30;

export function middleware(request) {
    if (!request.nextUrl.pathname.startsWith('/api/validar-cpf')) {
    return NextResponse.next();
    }

    const ip = request.headers.get('x-forwarded-for') ||
    request.headers.get('x-real-ip') || 'unknown';

    const now = Date.now();
    const windowStart = now - WINDOW_MS;

    if (!rateLimit.has(ip)) {
    rateLimit.set(ip, []);
    }

    const requests = rateLimit.get(ip).filter((t) => t > windowStart);
    requests.push(now);
    rateLimit.set(ip, requests);

    if (requests.length > MAX_REQUESTS) {
    return Response.json(
    { success: false, message: 'Limite de requisições excedido' },
    { status: 429, headers: { 'Retry-After': '60' } }
    );
    }

    return NextResponse.next();
}

export const config = {
    matcher: '/api/validar-cpf/:path*',
};

Consumindo a API do lado do cliente

Crie um hook personalizado para facilitar o uso nos componentes React:

// hooks/useCpfValidation.js
'use client';

import { useState, useCallback } from 'react';

export function useCpfValidation() {
    const [loading, setLoading] = useState(false);
    const [resultado, setResultado] = useState(null);
    const [erro, setErro] = useState(null);

    const validar = useCallback(async (cpf) => {
    setLoading(true);
    setErro(null);
    setResultado(null);

    try {
    const response = await fetch('/api/validar-cpf', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ cpf }),
    });

    const data = await response.json();

    if (response.ok && data.success) {
    setResultado(data);
    } else {
    setErro(data.message || 'Erro na validação');
    }
    } catch {
    setErro('Erro de conexão');
    } finally {
    setLoading(false);
    }
    }, []);

    return { validar, loading, resultado, erro };
}

Perguntas frequentes

Por que validar o CPF no servidor e não apenas no cliente em Next.js?

A validação no cliente pode ser burlada por qualquer pessoa com acesso às ferramentas de desenvolvedor do navegador: basta interceptar a requisição e enviar um CPF diferente do que foi validado. A validação no Route Handler — no servidor — garante que todo CPF processado pela sua lógica de negócio passou pela verificação, independente do que o cliente enviou.

Qual a diferença entre a validação algorítmica e a consulta à API de CPF?

A validação algorítmica verifica se os dígitos verificadores são matematicamente corretos — ela detecta CPFs mal formatados ou fabricados, mas não confirma se o CPF pertence a uma pessoa real. A consulta à API da CPFHub.io acessa a base da Receita Federal e retorna nome, gênero e data de nascimento do titular, confirmando que o CPF existe e está ativo.

Como o rate limiting no middleware protege o endpoint de validação?

O middleware limita cada IP a 30 requisições por minuto. Isso previne ataques de força bruta que tentam descobrir CPFs válidos enviando sequências numéricas em massa. O rate limiting acontece antes de qualquer lógica de negócio, reduzindo carga no servidor e nas chamadas à API externa. Para mais detalhes sobre middleware no Next.js, consulte a documentação oficial do Next.js.

A API da CPFHub.io retorna erro 429 quando o limite do plano é atingido?

Não. A API da CPFHub.io nunca retorna HTTP 429 nem bloqueia requisições por limite de plano. Ao ultrapassar o limite mensal (50 consultas no plano gratuito ou 1.000 no Pro), as consultas adicionais são cobradas automaticamente a R$0,15 cada — o fluxo de validação continua sem interrupções.


Conclusão

Validar CPF no servidor com Next.js API Routes garante segurança e integridade dos dados. A chave de API fica protegida em variáveis de ambiente, a validação algorítmica evita chamadas desnecessárias à API externa e o rate limiting previne abuso. A combinação dos dois níveis de validação — local e via API — cobre tanto CPFs matematicamente inválidos quanto documentos que não existem na base da Receita Federal.

Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e implemente validação de CPF no servidor do seu projeto Next.js hoje mesmo.

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