Projeto 5: Buscador de CEP com Fetch (ViaCEP)

Crie uma aplicação onde o usuário digita um CEP e o sistema busca, via fetch, logradouro, bairro, cidade e estado.

Este projeto fecha o submódulo com chave de ouro, porque conecta tudo: interface, eventos, validação, requisição HTTP, tratamento de erros e atualização dinâmica da tela.

Pense nele como um “atendente digital”:

  • o usuário informa um CEP,
  • o JavaScript consulta uma base externa (API),
  • e devolve os dados de endereço sem recarregar a página.

O que este projeto consolida

Com o buscador de CEP, você pratica:

  • Eventos de formulário (submit).
  • Validação de entrada.
  • Requisições HTTP com fetch.
  • Conversão de resposta para JSON.
  • Assincronicidade com async/await.
  • Tratamento de erro com try...catch.
  • Manipulação do DOM para exibir resultados e mensagens.
  • Boas práticas de experiência do usuário (estado de carregamento, mensagens claras).

Fluxo mental do projeto

A lógica principal:

  1. Usuário digita um CEP.
  2. Sistema limpa caracteres não numéricos.
  3. Sistema valida se tem 8 dígitos.
  4. Faz requisição para a API ViaCEP.
  5. Se vier sucesso, exibe rua, bairro, cidade e estado.
  6. Se vier erro, mostra mensagem amigável.
  7. Tudo isso sem recarregar a página.

Exemplo completo (HTML + CSS + JS em um único arquivo)

<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Buscador de CEP</title>
  <style>
    body {
      margin: 0;
      min-height: 100vh;
      display: grid;
      place-items: center;
      background: #f5f7fb;
      font-family: Arial, sans-serif;
    }

    .card {
      width: 100%;
      max-width: 420px;
      background: #fff;
      border-radius: 12px;
      box-shadow: 0 10px 24px rgba(0, 0, 0, 0.08);
      padding: 20px;
    }

    h1 {
      margin-top: 0;
      text-align: center;
      font-size: 1.3rem;
      color: #222;
    }

    form {
      display: flex;
      gap: 8px;
      margin-bottom: 12px;
    }

    input {
      flex: 1;
      padding: 10px;
      border: 1px solid #cfd6e4;
      border-radius: 8px;
      font-size: 0.95rem;
    }

    button {
      border: none;
      background: #2563eb;
      color: white;
      padding: 10px 14px;
      border-radius: 8px;
      cursor: pointer;
    }

    button:hover {
      background: #1f54c7;
    }

    #mensagem {
      min-height: 20px;
      margin: 8px 0;
      color: #374151;
      font-size: 0.95rem;
    }

    .erro {
      color: #b91c1c;
    }

    .sucesso {
      color: #065f46;
    }

    .resultado {
      background: #eef2ff;
      border-radius: 8px;
      padding: 12px;
      font-size: 0.95rem;
      color: #1e3a8a;
      line-height: 1.6;
    }
  </style>
</head>
<body>
  <main class="card">
    <h1>Buscador de CEP</h1>

    <form id="form-cep">
      <input id="cep" type="text" placeholder="Digite o CEP (ex: 01001000)" maxlength="9" />
      <button type="submit">Buscar</button>
    </form>

    <p id="mensagem"></p>

    <div id="resultado" class="resultado" style="display: none;"></div>
  </main>

  <script>
    const form = document.getElementById("form-cep");
    const inputCep = document.getElementById("cep");
    const mensagem = document.getElementById("mensagem");
    const resultado = document.getElementById("resultado");

    function limparResultado() {
      resultado.style.display = "none";
      resultado.innerHTML = "";
    }

    function mostrarMensagem(texto, tipo = "") {
      mensagem.textContent = texto;
      mensagem.className = tipo;
    }

    function cepValido(cep) {
      return /^[0-9]{8}$/.test(cep);
    }

    async function buscarCep(cep) {
      const url = `https://viacep.com.br/ws/${cep}/json/`;

      const resposta = await fetch(url);

      if (!resposta.ok) {
        throw new Error("Falha na comunicação com o servidor.");
      }

      const dados = await resposta.json();

      if (dados.erro) {
        throw new Error("CEP não encontrado.");
      }

      return dados;
    }

    function renderizarEndereco(dados) {
      resultado.innerHTML = `
        <strong>Logradouro:</strong> ${dados.logradouro || "-"}<br>
        <strong>Bairro:</strong> ${dados.bairro || "-"}<br>
        <strong>Cidade:</strong> ${dados.localidade || "-"}<br>
        <strong>Estado:</strong> ${dados.uf || "-"}
      `;
      resultado.style.display = "block";
    }

    form.addEventListener("submit", async (event) => {
      event.preventDefault();

      limparResultado();
      mostrarMensagem("");

      const cepDigitado = inputCep.value.replace(/\D/g, "");

      if (!cepValido(cepDigitado)) {
        mostrarMensagem("Digite um CEP válido com 8 números.", "erro");
        return;
      }

      try {
        mostrarMensagem("Buscando CEP...", "");
        const dados = await buscarCep(cepDigitado);
        renderizarEndereco(dados);
        mostrarMensagem("CEP encontrado com sucesso!", "sucesso");
      } catch (erro) {
        mostrarMensagem(erro.message, "erro");
      }
    });
  </script>
</body>
</html>

Conceitos-chave que aparecem aqui

  • fetch(...): faz a requisição para a API.
  • await: espera a resposta sem travar a interface.
  • try...catch: captura e trata erros de forma amigável.
  • Regex /\D/g: remove caracteres que não são números.
  • Validação de CEP: evita requisições desnecessárias.
  • Atualização do DOM: mostra mensagens e dados na tela em tempo real.

Boas práticas usadas

  • Validar antes de consultar API.
  • Exibir estado de carregamento.
  • Separar funções por responsabilidade.
  • Tratar erros de rede e de CEP inexistente.
  • Mensagens claras para o usuário final.

Ideias para evoluir o projeto

  • Máscara de CEP automática (00000-000).
  • Salvar histórico de buscas no localStorage.
  • Buscar CEP ao sair do campo (blur) além do botão.
  • Adicionar botão para limpar resultados.
  • Usar debounce para evitar chamadas excessivas.