Implementar debounce em campos de CPF que consultam API em tempo real significa atrasar a requisição até o usuário parar de digitar, evitando disparar dezenas de chamadas desnecessárias a cada tecla. Com um delay de 500–600ms e validação sintática no front-end, é possível reduzir o consumo de créditos da API em até 90% sem prejudicar a experiência do usuário.
Introdução
Formulários que consultam uma API de CPF em tempo real — enquanto o usuário digita — oferecem uma experiência dinâmica e ágil. No entanto, sem o uso de debounce, cada tecla pressionada pode disparar uma requisição à API, resultando em dezenas de chamadas desnecessárias que consomem créditos e sobrecarregam o servidor. Este guia mostra como implementar debounce em JavaScript puro e React, com a consulta sendo feita no backend.
O que é debounce
Debounce é uma técnica que atrasa a execução de uma função até que um período de inatividade tenha passado. Em vez de executar a função a cada evento (como cada tecla digitada), o debounce aguarda o usuário parar de digitar por um intervalo definido antes de executar a ação.
Sem debounce
Quando o usuário digita o CPF "12345678900", o campo dispara 11 requisições:
Digitou "1" -> Requisição 1
Digitou "12" -> Requisição 2
Digitou "123" -> Requisição 3
...
Digitou "12345678900" -> Requisição 11
Resultado: 10 requisições desperdiçadas, apenas a última é útil.
Com debounce (500ms)
O campo aguarda 500ms após a última tecla antes de disparar a requisição:
Digitou "1" -> Aguarda 500ms...
Digitou "12" -> Reinicia timer, aguarda 500ms...
Digitou "123" -> Reinicia timer, aguarda 500ms...
...
Digitou "12345678900" -> Aguarda 500ms... -> Requisição 1 (única)
Resultado: 1 requisição, economia de 10 créditos.
Implementação em JavaScript puro
Função de debounce
function debounce(fn, delay) {
let timerId = null;
return function (...args) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
fn.apply(this, args);
timerId = null;
}, delay);
};
}
Aplicando ao campo de CPF
const campoCPF = document.getElementById('cpf-input');
const resultadoDiv = document.getElementById('resultado');
async function consultarCPFNoBackend(cpf) {
// A consulta à API deve ser feita pelo backend
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(`/api/consultar-cpf?cpf=${cpf}`, {
signal: controller.signal
});
clearTimeout(timeoutId);
const dados = await response.json();
if (dados.success) {
resultadoDiv.textContent = `Nome: ${dados.data.name}`;
resultadoDiv.className = 'sucesso';
} else {
resultadoDiv.textContent = 'CPF não encontrado.';
resultadoDiv.className = 'erro';
}
} catch (error) {
clearTimeout(timeoutId);
resultadoDiv.textContent = 'Erro ao consultar CPF.';
resultadoDiv.className = 'erro';
}
}
function validarEConsultar(event) {
const cpf = event.target.value.replace(/\D/g, '');
if (cpf.length < 11) {
resultadoDiv.textContent = '';
return;
}
if (cpf.length === 11) {
consultarCPFNoBackend(cpf);
}
}
// Debounce de 600ms
campoCPF.addEventListener('input', debounce(validarEConsultar, 600));
Endpoint no backend (Express)
const express = require('express');
const app = express();
app.get('/api/consultar-cpf', async (req, res) => {
const cpf = req.query.cpf;
if (!cpf || cpf.length !== 11) {
return res.status(400).json({ error: 'CPF inválido' });
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(
`https://api.cpfhub.io/cpf/${cpf}`,
{
headers: {
'x-api-key': process.env.CPFHUB_API_KEY,
'Accept': 'application/json'
},
signal: controller.signal
}
);
clearTimeout(timeoutId);
const dados = await response.json();
res.json(dados);
} catch (error) {
clearTimeout(timeoutId);
res.status(500).json({ error: 'Falha na consulta' });
}
});
app.listen(3000);
Implementação em React
import { useState, useCallback, useRef } from 'react';
function useDebounce(fn, delay) {
const timerRef = useRef(null);
return useCallback((...args) => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
fn(...args);
}, delay);
}, [fn, delay]);
}
function CampoCPF() {
const [cpf, setCpf] = useState('');
const [resultado, setResultado] = useState(null);
const [carregando, setCarregando] = useState(false);
const consultarCPF = useCallback(async (valor) => {
const cpfLimpo = valor.replace(/\D/g, '');
if (cpfLimpo.length !== 11) {
setResultado(null);
return;
}
setCarregando(true);
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response = await fetch(
`/api/consultar-cpf?cpf=${cpfLimpo}`,
{ signal: controller.signal }
);
clearTimeout(timeoutId);
const dados = await response.json();
setResultado(dados);
} catch (error) {
setResultado({ error: 'Falha na consulta' });
} finally {
setCarregando(false);
}
}, []);
const consultarComDebounce = useDebounce(consultarCPF, 600);
const handleChange = (e) => {
const valor = e.target.value;
setCpf(valor);
consultarComDebounce(valor);
};
return (
<div>
<input
type="text"
value={cpf}
onChange={handleChange}
placeholder="Digite o CPF"
maxLength={14}
/>
{carregando && <p>Consultando...</p>}
{resultado?.success && <p>Nome: {resultado.data.name}</p>}
{resultado?.error && <p>Erro: {resultado.error}</p>}
</div>
);
}
Escolhendo o delay ideal
O delay do debounce deve equilibrar responsividade com economia de requisições:
| Delay | Comportamento | Quando usar |
|---|---|---|
| 300ms | Muito responsivo, mais requisições | Campos com autocomplete |
| 500-600ms | Equilíbrio ideal | Campos de CPF (recomendado) |
| 1000ms | Menos responsivo, menos requisições | Processos batch ou de baixa urgência |
Para campos de CPF, 500-600ms é o valor recomendado. O usuário digita os 11 dígitos em aproximadamente 3-5 segundos, e o debounce disparará a consulta logo após ele terminar de digitar.
Combinando debounce com validação sintática
Antes de enviar a requisição ao backend, valide o CPF sintaticamente no front-end para evitar chamadas desnecessárias:
function validarCPFSintatico(cpf) {
cpf = cpf.replace(/\D/g, '');
if (cpf.length !== 11) return false;
if (/^(\d)\1{10}$/.test(cpf)) return false;
let soma = 0;
for (let i = 0; i < 9; i++) soma += parseInt(cpf[i]) * (10 - i);
let resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
if (resto !== parseInt(cpf[9])) return false;
soma = 0;
for (let i = 0; i < 10; i++) soma += parseInt(cpf[i]) * (11 - i);
resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
return resto === parseInt(cpf[10]);
}
const consultarComDebounce = debounce((valor) => {
const cpfLimpo = valor.replace(/\D/g, '');
if (cpfLimpo.length !== 11) return;
// Validar formato antes de consultar
if (!validarCPFSintatico(cpfLimpo)) {
resultadoDiv.textContent = 'CPF com formato inválido.';
return;
}
consultarCPFNoBackend(cpfLimpo);
}, 600);
Boas práticas
-
Sempre consulte a API pelo backend -- Nunca exponha a chave de API no front-end. O debounce controla o momento da chamada, mas a requisição deve partir do servidor.
-
Mostre indicador de carregamento -- Enquanto a consulta está em andamento, exiba um spinner ou texto "Consultando..." para que o usuário saiba que algo está acontecendo.
-
Cancele requisições anteriores -- Se o usuário digitar um novo CPF antes da resposta chegar, cancele a requisição anterior usando AbortController.
-
Valide sintaticamente no front-end -- Rejeite CPFs com formato inválido antes de enviar ao backend.
-
Use máscara de input -- Formate o campo como XXX.XXX.XXX-XX para facilitar a digitação e prevenir erros.
Perguntas frequentes
Qual o delay de debounce ideal para campos de CPF?
Para campos de CPF, o intervalo de 500–600ms é o equilíbrio ideal entre responsividade e economia de requisições. O usuário leva em média 3–5 segundos para digitar 11 dígitos, então o debounce dispara naturalmente logo após ele concluir. Valores abaixo de 300ms aumentam o consumo de créditos; acima de 1.000ms deixam o formulário lento.
A API CPFHub.io bloqueia quando o limite do plano é atingido?
Não. A CPFHub.io nunca retorna HTTP 429 nem bloqueia requisições. Ao ultrapassar o limite do plano, cada consulta extra é cobrada a R$0,15. O plano gratuito oferece 50 consultas/mês sem cartão de crédito; o plano Pro inclui 1.000 consultas por R$149/mês com excedente a 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.
Devo validar o CPF sintaticamente antes de chamar a API?
Sim, sempre. A validação sintática no front-end — verificação dos dígitos verificadores pelo algoritmo da Receita Federal — descarta CPFs inválidos antes mesmo de chegar ao backend, economizando requisições e melhorando a UX com feedback imediato ao usuário.
Conclusão
O debounce é uma técnica simples mas essencial para campos de CPF que consultam API em tempo real. Ao aguardar o usuário terminar de digitar antes de disparar a requisição, ele economiza créditos de API e mantém a experiência fluida. Combinado com validação sintática no front-end e consulta no backend, o resultado é uma integração eficiente e segura.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e implemente debounce em seus formulários de CPF em menos de 30 minutos.
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.



