APRENDIZADO & COGNIÇÃO

Tudo que você precisa saber sobre CEP em JavaScript: validação, máscara, API e debounce

Quem já construiu um formulário de endereço no Brasil sabe que o campo de CEP parece simples, mas esconde algumas…

Quem já construiu um formulário de endereço no Brasil sabe que o campo de CEP parece simples, mas esconde algumas armadilhas. Neste artigo, vou cobrir do básico ao avançado: estrutura do CEP, validação com Regex, máscara de input, integração com a API ViaCEP, tratamento de erros e debounce — tudo em JavaScript puro, sem dependências externas.


1. O que é o CEP e como ele é estruturado?

O CEP (Código de Endereçamento Postal) é composto por 8 dígitos numéricos, divididos em dois grupos:

XXXXX - XXX  |       |Faixa   Sufixo
  • Os 5 primeiros dígitos identificam a localidade (região, cidade, bairro)
  • Os 3 últimos dígitos identificam o logradouro ou setor específico

Exemplos reais:

CEP Local
01001-000 Praça da Sé, São Paulo – SP
20040-020 Centro, Rio de Janeiro – RJ
30112-000 Centro, Belo Horizonte – MG

Detalhe importante: nem todo CEP aponta para uma rua específica. Grandes empresas e órgãos públicos possuem CEPs exclusivos (ex: Correios, Receita Federal).


2. Limpeza de Input (o primeiro passo que muitos pulam)

Antes de qualquer validação, precisamos normalizar o que o usuário digitou — removendo traços, espaços e qualquer caractere não numérico:

function limparCEP(cep) {    return cep.replace(/D/g, "");}// Exemplos:limparCEP("01001-000");  // "01001000"limparCEP("01001 000"); // "01001000"limparCEP(" 01001000 "); // "01001000"

A regex /D/g substitui qualquer caractere que não seja dígito (D) por string vazia, globalmente (g).


3. Aplicando Máscara ao Input

Para formatar o CEP enquanto o usuário digita (no formato XXXXX-XXX):

function formatarCEP(cep) {    const cepLimpo = limparCEP(cep);    // Aplica máscara apenas quando tiver 8 dígitos completos    if (cepLimpo.length === 8) {        return cepLimpo.replace(/^(d{5})(d{3})$/, "$1-$2");    }    return cepLimpo;}console.log(formatarCEP("01001000"));  // "01001-000"console.log(formatarCEP("0100100"));   // "0100100" (incompleto, sem máscara)

Para aplicar a máscara em tempo real no input HTML:

const inputCEP = document.getElementById("cep");inputCEP.addEventListener("input", (e) => {    const cepLimpo = limparCEP(e.target.value);    // Limita a 8 dígitos    const cepTruncado = cepLimpo.slice(0, 8);    // Aplica a máscara progressiva enquanto digita    if (cepTruncado.length > 5) {        e.target.value = cepTruncado.replace(/^(d{5})(d{0,3})$/, "$1-$2");    } else {        e.target.value = cepTruncado;    }});

Por que máscara progressiva? Se você só aplicar a máscara quando tiver 8 dígitos, o usuário vê 01001000 e só depois 01001-000. Com a máscara progressiva, ele vê 01001- assim que digita o 6º caractere — UX muito melhor.


4. Validação com Regex (antes de chamar qualquer API)

Nunca dispare uma requisição para a API sem validar antes. Isso economiza requisições desnecessárias e protege sua aplicação:

function validarCEP(cep) {    const cepLimpo = limparCEP(cep);        // Deve ter exatamente 8 dígitos numéricos    const regexCEP = /^[0-9]{8}$/;        return regexCEP.test(cepLimpo);}console.log(validarCEP("01001-000")); // trueconsole.log(validarCEP("00000-000")); // true (formalmente válido, mas pode não existir)console.log(validarCEP("0100100"));   // false (só 7 dígitos)console.log(validarCEP("abcde-fgh")); // false

Atenção: A validação via Regex apenas verifica o formato, não se o CEP existe de fato nos Correios. Para isso, precisamos consultar a API.


5. Integrando com a API ViaCEP

O ViaCEP é um serviço gratuito, sem autenticação e muito confiável para consultas de CEP no Brasil.

async function consultarCEP(cep) {    const cepLimpo = limparCEP(cep);    // Validação antes da requisição    if (!validarCEP(cepLimpo)) {        throw new Error("Formato de CEP inválido.");    }    const response = await fetch(`https://viacep.com.br/ws/${cepLimpo}/json/`);    if (!response.ok) {        throw new Error(`Erro HTTP: ${response.status}`);    }    const data = await response.json();    // A API retorna { "erro": true } quando o CEP não existe    if (data.erro) {        throw new Error("CEP não encontrado.");    }    return data;}

Estrutura do objeto retornado pela API:

{  "cep": "01001-000",  "logradouro": "Praça da Sé",  "complemento": "lado ímpar",  "bairro": "Sé",  "localidade": "São Paulo",  "uf": "SP",  "ibge": "3550308",  "ddd": "11",  "siafi": "7107"}

Usando a função com tratamento de erros:

async function preencherEndereco(cep) {    try {        const endereco = await consultarCEP(cep);                document.getElementById("logradouro").value = endereco.logradouro;        document.getElementById("bairro").value = endereco.bairro;        document.getElementById("cidade").value = endereco.localidade;        document.getElementById("uf").value = endereco.uf;            } catch (error) {        console.error("Falha ao buscar CEP:", error.message);        // Aqui você pode mostrar uma mensagem de erro na UI    }}

6. Debounce: a técnica que evita spam de requisições

Um erro clássico é disparar a busca a cada tecla digitada. Com debounce, você espera o usuário parar de digitar antes de fazer a requisição:

function debounce(fn, delay = 500) {    let timer;    return function (...args) {        clearTimeout(timer);        timer = setTimeout(() => fn.apply(this, args), delay);    };}// Cria uma versão "debounced" da função de buscaconst buscarCEPComDebounce = debounce(async (cep) => {    if (validarCEP(cep)) {        await preencherEndereco(cep);    }}, 600);// Conecta ao inputdocument.getElementById("cep").addEventListener("input", (e) => {    buscarCEPComDebounce(e.target.value);});

onBlur vs debounce — quando usar cada um?

Situação Recomendação
Formulário simples, usuário já sabe o CEP onBlur (mais simples, menos requisições)
UX fluida, preenchimento automático rápido debounce no onChange (300–600ms)
Mobile com teclado numérico onBlur (evita busca durante digitação lenta)

7. Feedback visual durante o carregamento

Um detalhe que melhora muito a UX: mostrar ao usuário que a busca está acontecendo:

async function buscarComFeedback(cep) {    const input = document.getElementById("cep");    const status = document.getElementById("cep-status");    if (!validarCEP(cep)) return;    // Estado de carregamento    input.disabled = true;    status.textContent = "Buscando...";    try {        const endereco = await consultarCEP(cep);        preencherCampos(endereco);        status.textContent = "✓ Endereço encontrado";        status.style.color = "green";    } catch (error) {        status.textContent = "✗ " + error.message;        status.style.color = "red";    } finally {        // Sempre reabilita o input, mesmo em caso de erro        input.disabled = false;    }}

8. Tratamento de edge cases que passam despercebidos

// CEPs que formalmente são válidos mas costumam não existir:const CEPS_INVALIDOS_CONHECIDOS = [    "00000000", // Zerado    "11111111", // Sequência repetida    "99999999", // Máximo];function cepSuspeito(cep) {    const limpo = limparCEP(cep);        // Sequências repetidas (00000000, 11111111, etc.)    if (/^(d)1{7}$/.test(limpo)) return true;        return CEPS_INVALIDOS_CONHECIDOS.includes(limpo);}console.log(cepSuspeito("00000000")); // trueconsole.log(cepSuspeito("12345678")); // falseconsole.log(cepSuspeito("01001000")); // false

Solução Completa — Juntando tudo

// utils/cep.jsconst limparCEP = (cep) => cep.replace(/D/g, "");const formatarCEP = (cep) => {    const limpo = limparCEP(cep).slice(0, 8);    return limpo.length > 5        ? limpo.replace(/^(d{5})(d{0,3})$/, "$1-$2")        : limpo;};const validarCEP = (cep) => /^[0-9]{8}$/.test(limparCEP(cep));const consultarCEP = async (cep) => {    const limpo = limparCEP(cep);    if (!validarCEP(limpo)) throw new Error("CEP inválido");    const res = await fetch(`https://viacep.com.br/ws/${limpo}/json/`);    if (!res.ok) throw new Error(`Erro ${res.status}`);    const data = await res.json();    if (data.erro) throw new Error("CEP não encontrado");    return data;};const debounce = (fn, ms = 500) => {    let t;    return (...args) => {        clearTimeout(t);        t = setTimeout(() => fn(...args), ms);    };};export { limparCEP, formatarCEP, validarCEP, consultarCEP, debounce };

Conclusão

Um campo de CEP bem implementado combina:

  1. Limpeza — normaliza o input antes de qualquer processamento
  2. Máscara progressiva — melhora a UX durante a digitação
  3. Validação local — evita requisições desnecessárias
  4. Debounce inteligente — escolha baseada no contexto (onBlur vs onChange)
  5. Tratamento de erros robusto — distingue erro de rede de CEP inexistente
  6. Feedback visual — mantém o usuário informado durante a busca

Para testar rapidamente um CEP durante o desenvolvimento sem montar um formulário inteiro, ferramentas como o cep-br.com permitem validar e consultar o retorno da API de forma direta — útil para debugar edge cases antes de integrar no código.


Pergunta para a comunidade: Vocês costumam usar onBlur ou debounce no onChange nos formulários de vocês? E quando o CEP não é encontrado, vocês limpam os campos de endereço ou mantêm o que estava preenchido antes? Qual abordagem tem funcionado melhor na UX de vocês?

Fonte: TabNews

Perguntas frequentes

Respostas institucionais sobre esta cobertura, fontes e limites editoriais.

Sobre o que trata “Tudo que você precisa saber sobre CEP em JavaScript: validação,…”?
Quem já construiu um formulário de endereço no Brasil sabe que o campo de CEP parece simples, mas esconde algumas armadilhas. Neste artigo, vou cobrir do básico ao avançado: estrutura do CEP, validação com Regex, máscara…
1. O que é o CEP e como ele é estruturado?
O CEP (Código de Endereçamento Postal) é composto por 8 dígitos numéricos, divididos em dois grupos:XXXXX - XXX | |Faixa SufixoOs 5 primeiros dígitos identificam a localidade (região, cidade, bairro)Os 3 últimos dígitos identificam o logradouro ou setor específicoExemplos reais:CEPLocal01001-000Praça da Sé,…
O que a matéria explica sobre “2. Limpeza de Input (o primeiro passo que muitos pulam)”?
Antes de qualquer validação, precisamos normalizar o que o usuário digitou — removendo traços, espaços e qualquer caractere não numérico:function limparCEP(cep) { return cep.replace(/D/g, "");}// Exemplos:limparCEP("01001-000"); // "01001000"limparCEP("01001 000"); // "01001000"limparCEP(" 01001000 "); // "01001000"A regex /D/g substitui qualquer caractere que não…
O que a matéria explica sobre “3. Aplicando Máscara ao Input”?
Para formatar o CEP enquanto o usuário digita (no formato XXXXX-XXX):function formatarCEP(cep) { const cepLimpo = limparCEP(cep); // Aplica máscara apenas quando tiver 8 dígitos completos if (cepLimpo.length === 8) { return cepLimpo.replace(/^(d{5})(d{3})$/, "$1-$2"); } return cepLimpo;}console.log(formatarCEP("01001000")); // "01001-000"console.log(formatarCEP("0100100")); // "0100100" (incompleto,…
O que a matéria explica sobre “4. Validação com Regex (antes de chamar qualquer API)”?
Nunca dispare uma requisição para a API sem validar antes. Isso economiza requisições desnecessárias e protege sua aplicação:function validarCEP(cep) { const cepLimpo = limparCEP(cep); // Deve ter exatamente 8 dígitos numéricos const regexCEP = /^[0-9]{8}$/; return regexCEP.test(cepLimpo);}console.log(validarCEP("01001-000")); // trueconsole.log(validarCEP("00000-000")); // true (formalmente…
Quem edita o conteúdo do Estrato Mente?
A cobertura é produzida e revisada pela equipe editorial do Estrato Mente, com editor-chefe responsável pela linha editorial. Conheça a redação em https://mente.estrato.cc/equipe/.
Quais fontes o Estrato prioriza?
Priorizamos fontes primárias (órgãos oficiais, balanços, estudos revisados, documentos públicos) e cruzamos informações antes da publicação, conforme a política editorial.
Como solicitar correção de uma matéria?
Envie o pedido pela página de Correções (https://mente.estrato.cc/correcoes/) ou Contato (https://mente.estrato.cc/contato/). Erros materiais são corrigidos com registro da atualização.
WhatsApp X LinkedIn
Compartilhar: LinkedIn
Rafael Gomes

Rafael Gomes

Rafael Gomes integra a equipe editorial do Estrato Mente, vertical da rede Estrato, na função de Repórter de Produtividade. O escopo permanente de cobertura inclui rotinas e trabalho profundo, com atenção ao leitor brasileiro que busca contexto factual, datas oficiais e implicações práticas. Na produção diária, prioriza fontes primárias (órgãos públicos, balanços, papers e documentos oficiais), cruza pelo menos duas referências independentes quando o tema é contestado e evita manchetes que prometam certeza onde há incerteza. Critérios de qualidade: lead com o fato principal, atribuição clara de autoria da equipe Estrato, atualização com selo quando há mudança material e linguagem acessível sem simplificar demais o risco ou o contexto. Trabalha sob revisão da mesa editorial e do editor-chefe do portal. Matérias YMYL recebem segunda leitura antes da publicação e podem ser atualizadas quando surgem novos dados oficiais. Limites: este perfil descreve o papel editorial na marca Estrato. O conteúdo publicado é informativo e não constitui aconselhamento médico, financeiro, jurídico ou profissional personalizado. Transparência ao leitor: metodologia em /metodologia/, ética em /etica-editorial/, correções em /correcoes/, equipe em /equipe/ e canal em /contato/ ([email protected]). Na página-mãe da rede, a bio ampliada e o arquivo de matérias ficam em estrato.cc/blog/, com link para o arquivo do autor em cada portal.

Deixe um comentário