Teoria sem prática é esquecida rápido. Neste artigo vamos consumir APIs públicas reais — sem cadastro, sem chave, sem custo — e construir mini-aplicações funcionais com cada uma.
O objetivo é duplo: solidificar tudo que aprendemos sobre Fetch, async/await e tratamento de erros, e ao mesmo tempo mostrar o quão poderoso o JavaScript se torna quando conectado ao mundo real.
As APIs que vamos usar
API URL Base Chave?
─────────────────────────────────────────────────────────────────
JSONPlaceholder https://jsonplaceholder.typicode.com Não
ViaCEP https://viacep.com.br Não
PokeAPI https://pokeapi.co/api/v2 Não
Open-Meteo https://api.open-meteo.com/v1 Não
IBGE https://servicodados.ibge.gov.br Não
DiceBear (Avatares) https://api.dicebear.com/7.x Não
Todas gratuitas, públicas e sem autenticação. Perfeito para aprender.
API 1 — ViaCEP: buscando endereços brasileiros
A ViaCEP é uma API brasileira que retorna dados de endereço a partir de um CEP. Extremamente útil em formulários de e-commerce e cadastro.
async function buscarCEP(cep) {
// Remove formatação — aceita "01310-100" ou "01310100"
const cepLimpo = cep.replace(/D/g, "");
if (cepLimpo.length !== 8) {
throw new Error("CEP deve ter 8 dígitos.");
}
const response = await fetch(`https://viacep.com.br/ws/${cepLimpo}/json/`);
if (!response.ok) {
throw new Error(`Erro ${response.status} ao consultar CEP.`);
}
const dados = await response.json();
// ViaCEP retorna { erro: true } para CEPs inválidos
if (dados.erro) {
throw new Error(`CEP ${cep} não encontrado.`);
}
return {
cep: dados.cep,
logradouro: dados.logradouro,
complemento: dados.complemento,
bairro: dados.bairro,
cidade: dados.localidade,
estado: dados.uf,
ibge: dados.ibge,
ddd: dados.ddd,
};
}
// Preenchimento automático de formulário
const inputCEP = document.querySelector("#cep");
const campos = {
logradouro: document.querySelector("#logradouro"),
bairro: document.querySelector("#bairro"),
cidade: document.querySelector("#cidade"),
estado: document.querySelector("#estado"),
};
function debounce(fn, delay) {
let timer;
return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); };
}
const preencherEndereco = debounce(async (cep) => {
if (cep.replace(/D/g, "").length !== 8) return;
inputCEP.style.borderColor = "";
try {
const endereco = await buscarCEP(cep);
campos.logradouro.value = endereco.logradouro;
campos.bairro.value = endereco.bairro;
campos.cidade.value = endereco.cidade;
campos.estado.value = endereco.estado;
// Foca no próximo campo relevante
document.querySelector("#numero")?.focus();
console.log(`✅ Endereço encontrado: ${endereco.logradouro}, ${endereco.cidade}/${endereco.estado}`);
} catch (erro) {
inputCEP.style.borderColor = "red";
console.error(erro.message);
}
}, 600);
inputCEP.addEventListener("input", (e) => {
preencherEndereco(e.target.value);
});
API 2 — IBGE: dados geográficos do Brasil
O IBGE disponibiliza uma API completa com estados, municípios e muito mais:
const IBGE_BASE = "https://servicodados.ibge.gov.br/api/v1";
async function buscarEstados() {
const response = await fetch(`${IBGE_BASE}/localidades/estados?orderBy=nome`);
const estados = await response.json();
return estados.map(e => ({
id: e.id,
sigla: e.sigla,
nome: e.nome,
regiao: e.regiao.nome,
}));
}
async function buscarMunicipios(siglaEstado) {
const response = await fetch(
`${IBGE_BASE}/localidades/estados/${siglaEstado}/municipios?orderBy=nome`
);
const municipios = await response.json();
return municipios.map(m => ({
id: m.id,
nome: m.nome,
}));
}
// Populando selects de estado/cidade encadeados
async function inicializarSelects() {
const selectEstado = document.querySelector("#estado");
const selectCidade = document.querySelector("#cidade");
// Carrega estados
const estados = await buscarEstados();
estados.forEach(estado => {
const option = document.createElement("option");
option.value = estado.sigla;
option.textContent = `${estado.nome} (${estado.sigla})`;
selectEstado.appendChild(option);
});
// Ao trocar estado, carrega cidades
selectEstado.addEventListener("change", async () => {
const sigla = selectEstado.value;
selectCidade.innerHTML = "<option>Carregando...</option>";
selectCidade.disabled = true;
try {
const municipios = await buscarMunicipios(sigla);
selectCidade.innerHTML = '<option value="">Selecione a cidade</option>';
municipios.forEach(m => {
const option = document.createElement("option");
option.value = m.id;
option.textContent = m.nome;
selectCidade.appendChild(option);
});
selectCidade.disabled = false;
} catch (erro) {
selectCidade.innerHTML = "<option>Erro ao carregar cidades</option>";
}
});
}
inicializarSelects();
API 3 — Open-Meteo: previsão do tempo
Open-Meteo é uma API meteorológica gratuita e de código aberto — sem cadastro, sem chave:
async function buscarPrevisao(latitude, longitude, dias = 7) {
const params = new URLSearchParams({
latitude,
longitude,
daily: [
"temperature_2m_max",
"temperature_2m_min",
"precipitation_sum",
"weathercode",
"windspeed_10m_max",
].join(","),
current_weather: true,
timezone: "America/Sao_Paulo",
forecast_days: dias,
});
const response = await fetch(
`https://api.open-meteo.com/v1/forecast?${params}`
);
if (!response.ok) throw new Error("Erro ao buscar previsão do tempo.");
const dados = await response.json();
// Interpretando o weathercode
const descricaoTempo = (codigo) => {
const codigos = {
0: { desc: "Céu limpo", icone: "☀️" },
1: { desc: "Predominantemente limpo", icone: "🌤️" },
2: { desc: "Parcialmente nublado", icone: "⛅" },
3: { desc: "Nublado", icone: "☁️" },
45: { desc: "Neblina", icone: "🌫️" },
51: { desc: "Garoa leve", icone: "🌦️" },
61: { desc: "Chuva leve", icone: "🌧️" },
71: { desc: "Neve leve", icone: "🌨️" },
80: { desc: "Pancadas de chuva", icone: "⛈️" },
95: { desc: "Tempestade", icone: "🌩️" },
};
return codigos[codigo] || { desc: "Variável", icone: "🌈" };
};
const { daily, current_weather } = dados;
return {
agora: {
temperatura: current_weather.temperature,
vento: current_weather.windspeed,
...descricaoTempo(current_weather.weathercode),
},
previsao: daily.time.map((data, i) => ({
data: new Date(data).toLocaleDateString("pt-BR", { weekday: "short", day: "2-digit", month: "2-digit" }),
maxima: daily.temperature_2m_max[i],
minima: daily.temperature_2m_min[i],
chuva: daily.precipitation_sum[i],
vento: daily.windspeed_10m_max[i],
...descricaoTempo(daily.weathercode[i]),
})),
};
}
// Buscando o tempo em algumas cidades brasileiras
async function tempoNoBrasil() {
const cidades = [
{ nome: "São Paulo", lat: -23.55, lon: -46.63 },
{ nome: "Rio de Janeiro", lat: -22.91, lon: -43.17 },
{ nome: "Curitiba", lat: -25.43, lon: -49.27 },
{ nome: "Recife", lat: -8.05, lon: -34.88 },
{ nome: "Manaus", lat: -3.10, lon: -60.02 },
];
const resultados = await Promise.all(
cidades.map(async (cidade) => {
const previsao = await buscarPrevisao(cidade.lat, cidade.lon, 1);
return { cidade: cidade.nome, ...previsao.agora };
})
);
console.log("
🌡️ TEMPO AGORA NO BRASIL
");
resultados.forEach(r => {
console.log(`${r.icone} ${r.cidade}: ${r.temperatura}°C — ${r.desc}`);
});
}
tempoNoBrasil();
API 4 — PokeAPI: construindo um Pokédex
const POKE_BASE = "https://pokeapi.co/api/v2";
// Cache em memória para evitar requisições repetidas
const cachePokemons = new Map();
async function buscarPokemon(nomeOuId) {
const chave = String(nomeOuId).toLowerCase();
if (cachePokemons.has(chave)) {
return cachePokemons.get(chave);
}
const response = await fetch(`${POKE_BASE}/pokemon/${chave}`);
if (response.status === 404) {
throw new Error(`Pokémon "${nomeOuId}" não encontrado.`);
}
if (!response.ok) throw new Error(`Erro ${response.status}`);
const dados = await response.json();
const pokemon = {
id: dados.id,
nome: dados.name,
nomeFormatado: dados.name.charAt(0).toUpperCase() + dados.name.slice(1),
tipos: dados.types.map(t => t.type.name),
altura: dados.height / 10, // decímetros → metros
peso: dados.weight / 10, // hectogramas → kg
habilidades: dados.abilities.map(a => a.ability.name),
stats: dados.stats.reduce((acc, s) => {
acc[s.stat.name] = s.base_stat;
return acc;
}, {}),
sprites: {
frente: dados.sprites.front_default,
costas: dados.sprites.back_default,
artwork: dados.sprites.other?.["official-artwork"]?.front_default,
},
experienciaBase: dados.base_experience,
};
cachePokemons.set(chave, pokemon);
return pokemon;
}
async function buscarEvolucoes(nomeOuId) {
// Busca a espécie primeiro
const especieResp = await fetch(`${POKE_BASE}/pokemon-species/${nomeOuId}`);
const especie = await especieResp.json();
// Busca a cadeia de evolução
const evolucaoResp = await fetch(especie.evolution_chain.url);
const evolucao = await evolucaoResp.json();
// Extrai a cadeia recursivamente
function extrairCadeia(chain) {
const resultado = [chain.species.name];
if (chain.evolves_to.length > 0) {
resultado.push(...extrairCadeia(chain.evolves_to[0]));
}
return resultado;
}
return extrairCadeia(evolucao.chain);
}
async function comparar(nome1, nome2) {
const [p1, p2] = await Promise.all([
buscarPokemon(nome1),
buscarPokemon(nome2),
]);
const stats = ["hp", "attack", "defense", "speed", "special-attack", "special-defense"];
console.log(`
⚔️ BATALHA: ${p1.nomeFormatado} vs ${p2.nomeFormatado}
`);
console.log(`${"STAT".padEnd(20)} ${"".padEnd(10, p1.nomeFormatado.slice(0,10))} ${"".padEnd(10, p2.nomeFormatado.slice(0,10))}`);
console.log("─".repeat(50));
let vitorias1 = 0, vitorias2 = 0;
stats.forEach(stat => {
const v1 = p1.stats[stat] || 0;
const v2 = p2.stats[stat] || 0;
const vencedor = v1 > v2 ? "←" : v1 < v2 ? "→" : "=";
if (v1 > v2) vitorias1++;
if (v2 > v1) vitorias2++;
console.log(`${stat.padEnd(20)} ${String(v1).padEnd(15)} ${v2} ${vencedor}`);
});
console.log("─".repeat(50));
const vencedor = vitorias1 > vitorias2
? p1.nomeFormatado
: vitorias2 > vitorias1
? p2.nomeFormatado
: "Empate";
console.log(`
🏆 Vencedor nos stats: ${vencedor}`);
}
// Exemplos
const pikachu = await buscarPokemon("pikachu");
console.log(`${pikachu.nomeFormatado} — Tipos: ${pikachu.tipos.join(", ")}`);
console.log(`Altura: ${pikachu.altura}m | Peso: ${pikachu.peso}kg`);
const evolucoes = await buscarEvolucoes("charmander");
console.log(`Cadeia de evolução: ${evolucoes.join(" → ")}`);
// charmander → charmeleon → charizard
await comparar("pikachu", "bulbasaur");
API 5 — DiceBear: gerando avatares dinâmicos
Uma API para gerar avatares SVG únicos — ótimo para perfis de usuário:
function gerarAvatarURL(seed, estilo = "adventurer", opcoes = {}) {
const {
tamanho = 128,
backgroundColor = "b6e3f4",
} = opcoes;
const params = new URLSearchParams({
seed,
size: tamanho,
backgroundColor,
});
return `https://api.dicebear.com/7.x/${estilo}/svg?${params}`;
}
// Estilos disponíveis
const estilos = [
"adventurer", "avataaars", "big-smile",
"bottts", "croodles", "fun-emoji",
"icons", "identicon", "initials",
"lorelei", "micah", "miniavs",
"notionists", "open-peeps", "personas",
"pixel-art", "rings", "shapes",
"thumbs",
];
// Gerar avatar baseado no nome do usuário
function avatarParaUsuario(nome, estilo = "adventurer") {
return gerarAvatarURL(nome, estilo);
}
// Galeria de avatares
async function criarGaleriaAvatares(usuarios) {
const container = document.querySelector("#galeria");
usuarios.forEach(usuario => {
const div = document.createElement("div");
div.className = "avatar-card";
const img = document.createElement("img");
img.src = avatarParaUsuario(usuario.nome);
img.alt = usuario.nome;
img.width = 80;
img.height = 80;
img.style.borderRadius = "50%";
const nome = document.createElement("p");
nome.textContent = usuario.nome;
div.append(img, nome);
container.appendChild(div);
});
}
// Uso
criarGaleriaAvatares([
{ nome: "Ana Paula" },
{ nome: "Carlos Silva" },
{ nome: "Beatriz Costa" },
]);
Aplicação completa — Dashboard multi-API
Vamos combinar ViaCEP, IBGE e Open-Meteo em uma única aplicação:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Dashboard Brasil</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0f1117;
--surface: #1a1d27;
--surface2: #232637;
--border: #2e3248;
--accent: #7c6ff7;
--text: #e4e6f0;
--muted: #8b8fa8;
}
body {
font-family: 'Segoe UI', sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
padding: 2rem 1rem;
}
.container { max-width: 900px; margin: 0 auto; }
h1 {
text-align: center;
font-size: 1.5rem;
margin-bottom: 2rem;
color: var(--text);
}
.busca-cep {
display: flex;
gap: .75rem;
margin-bottom: 2rem;
}
.busca-cep input {
flex: 1;
padding: .75rem 1rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-size: 1rem;
}
.busca-cep input:focus {
outline: none;
border-color: var(--accent);
}
.busca-cep button {
padding: .75rem 1.5rem;
background: var(--accent);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 700;
font-size: 1rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.5rem;
animation: aparecer .3s ease;
}
@keyframes aparecer {
from { opacity: 0; transform: translateY(-6px); }
to { opacity: 1; transform: translateY(0); }
}
.card-titulo {
font-size: .8rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: .1em;
margin-bottom: 1rem;
}
.card-valor {
font-size: 1.75rem;
font-weight: 700;
margin-bottom: .25rem;
}
.card-sub { font-size: .9rem; color: var(--muted); }
.endereco-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: .5rem;
}
.campo { font-size: .9rem; }
.campo span { display: block; color: var(--muted); font-size: .75rem; }
.previsao {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: .5rem;
}
.dia {
background: var(--surface2);
border-radius: 8px;
padding: .75rem .5rem;
text-align: center;
font-size: .85rem;
}
.dia .icone { font-size: 1.5rem; margin: .25rem 0; }
.dia .maxima { font-weight: 700; }
.dia .minima { color: var(--muted); }
.skeleton {
background: linear-gradient(90deg, var(--surface2) 25%, var(--border) 50%, var(--surface2) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
height: 1rem;
margin: .25rem 0;
}
@keyframes shimmer {
from { background-position: -200% 0; }
to { background-position: 200% 0; }
}
.erro-card {
background: rgba(248, 113, 113, .1);
border: 1px solid rgba(248, 113, 113, .3);
border-radius: 12px;
padding: 1rem 1.5rem;
color: #f87171;
margin-bottom: 1rem;
display: none;
}
.erro-card.visivel { display: block; }
</style>
</head>
<body>
<div class="container">
<h1>🇧🇷 Dashboard Brasil</h1>
<div class="busca-cep">
<input type="text" id="input-cep"
placeholder="Digite um CEP (ex: 01310-100)"
maxlength="9">
<button id="btn-buscar">Buscar</button>
</div>
<div class="erro-card" id="erro"></div>
<div id="resultado"></div>
</div>
<script>
const inputCEP = document.querySelector("#input-cep");
const btnBuscar = document.querySelector("#btn-buscar");
const erroEl = document.querySelector("#erro");
const resultado = document.querySelector("#resultado");
// ── CEP com máscara ──────────────────────────────
inputCEP.addEventListener("input", (e) => {
let v = e.target.value.replace(/D/g, "");
if (v.length > 5) v = v.slice(0, 5) + "-" + v.slice(5, 8);
e.target.value = v;
});
// ── APIs ─────────────────────────────────────────
async function buscarCEP(cep) {
const limpo = cep.replace(/D/g, "");
if (limpo.length !== 8) throw new Error("CEP deve ter 8 dígitos.");
const r = await fetch(`https://viacep.com.br/ws/${limpo}/json/`);
const d = await r.json();
if (d.erro) throw new Error(`CEP "${cep}" não encontrado.`);
return d;
}
async function buscarTempo(uf) {
// Coordenadas aproximadas das capitais por UF
const coords = {
SP: [-23.55, -46.63], RJ: [-22.91, -43.17], MG: [-19.92, -43.94],
RS: [-30.03, -51.22], PR: [-25.43, -49.27], SC: [-27.60, -48.55],
BA: [-12.97, -38.50], PE: [-8.05, -34.88], CE: [-3.73, -38.52],
PA: [-1.46, -48.50], AM: [-3.10, -60.02], GO: [-16.69, -49.25],
MT: [-15.60, -56.10], MS: [-20.44, -54.65], ES: [-20.32, -40.34],
MA: [-2.53, -44.30], RN: [-5.79, -35.21], PB: [-7.12, -34.86],
AL: [-9.67, -35.74], SE: [-10.91, -37.07], PI: [-5.09, -42.80],
TO: [-10.25, -48.33], RO: [-8.76, -63.90], AC: [-9.97, -67.81],
AP: [0.03, -51.07], RR: [2.82, -60.67], DF: [-15.78, -47.93],
};
const [lat, lon] = coords[uf] || [-15.78, -47.93];
const params = new URLSearchParams({
latitude: lat, longitude: lon,
current_weather: true,
daily: "temperature_2m_max,temperature_2m_min,weathercode",
timezone: "America/Sao_Paulo",
forecast_days: 5,
});
const r = await fetch(`https://api.open-meteo.com/v1/forecast?${params}`);
return await r.json();
}
async function buscarMunicipios(uf) {
const r = await fetch(
`https://servicodados.ibge.gov.br/api/v1/localidades/estados/${uf}/municipios`
);
const dados = await r.json();
return dados.length;
}
// ── Renderização ─────────────────────────────────
function codigoTempo(c) {
const m = {
0: "☀️", 1: "🌤️", 2: "⛅", 3: "☁️",
45: "🌫️", 51: "🌦️", 61: "🌧️", 71: "🌨️",
80: "⛈️", 95: "🌩️",
};
return m[c] || "🌈";
}
function renderizarSkeleton() {
resultado.innerHTML = `
<div class="grid">
<div class="card">
<div class="card-titulo">Carregando endereço...</div>
<div class="skeleton" style="width:70%"></div>
<div class="skeleton" style="width:50%"></div>
<div class="skeleton" style="width:60%"></div>
</div>
<div class="card">
<div class="card-titulo">Carregando clima...</div>
<div class="skeleton" style="width:40%; height: 2rem"></div>
<div class="skeleton" style="width:60%"></div>
</div>
</div>`;
}
function renderizarDados(cep, tempo, totalMunicipios) {
const { current_weather, daily } = tempo;
const iconeAgora = codigoTempo(current_weather.weathercode);
const diasHTML = daily.time.slice(0, 5).map((data, i) => `
<div class="dia">
<div>${new Date(data + "T12:00:00").toLocaleDateString("pt-BR", { weekday: "short" })}</div>
<div class="icone">${codigoTempo(daily.weathercode[i])}</div>
<div class="maxima">${Math.round(daily.temperature_2m_max[i])}°</div>
<div class="minima">${Math.round(daily.temperature_2m_min[i])}°</div>
</div>
`).join("");
resultado.innerHTML = `
<div class="grid">
<div class="card">
<div class="card-titulo">📍 Endereço</div>
<div class="endereco-grid">
<div class="campo">
<span>Logradouro</span>
${cep.logradouro || "—"}
</div>
<div class="campo">
<span>Bairro</span>
${cep.bairro || "—"}
</div>
<div class="campo">
<span>Cidade</span>
${cep.localidade}
</div>
<div class="campo">
<span>Estado</span>
${cep.uf}
</div>
<div class="campo">
<span>DDD</span>
(${cep.ddd})
</div>
<div class="campo">
<span>Código IBGE</span>
${cep.ibge}
</div>
</div>
</div>
<div class="card">
<div class="card-titulo">🌡️ Clima Agora (capital)</div>
<div class="card-valor">${iconeAgora} ${current_weather.temperature}°C</div>
<div class="card-sub">Vento: ${current_weather.windspeed} km/h</div>
</div>
<div class="card">
<div class="card-titulo">🏙️ Estado em números</div>
<div class="card-valor">${totalMunicipios.toLocaleString("pt-BR")}</div>
<div class="card-sub">Municípios em ${cep.uf}</div>
</div>
</div>
<div class="card">
<div class="card-titulo">📅 Previsão para os próximos 5 dias</div>
<div class="previsao" style="margin-top:.75rem">${diasHTML}</div>
</div>
`;
}
// ── Busca principal ───────────────────────────────
async function buscar() {
const cep = inputCEP.value.trim();
if (!cep) return;
erroEl.classList.remove("visivel");
renderizarSkeleton();
btnBuscar.disabled = true;
btnBuscar.textContent = "Buscando...";
try {
// Busca CEP primeiro (necessário para saber o estado)
const dadosCEP = await buscarCEP(cep);
// Busca clima e municípios em paralelo
const [tempo, totalMunicipios] = await Promise.all([
buscarTempo(dadosCEP.uf),
buscarMunicipios(dadosCEP.uf),
]);
renderizarDados(dadosCEP, tempo, totalMunicipios);
} catch (erro) {
resultado.innerHTML = "";
erroEl.textContent = `⚠️ ${erro.message}`;
erroEl.classList.add("visivel");
} finally {
btnBuscar.disabled = false;
btnBuscar.textContent = "Buscar";
}
}
btnBuscar.addEventListener("click", buscar);
inputCEP.addEventListener("keydown", (e) => {
if (e.key === "Enter") buscar();
});
// Carrega um CEP de exemplo
inputCEP.value = "01310-100";
buscar();
</script>
</body>
</html>
Boas práticas ao consumir APIs públicas
// ✅ 1. Respeite os rate limits — não faça spam de requisições
// Use debounce em campos de busca
// ✅ 2. Faça cache de respostas que não mudam frequentemente
const cache = new Map();
async function comCache(url, ttl = 60000) {
if (cache.has(url)) {
const { dados, timestamp } = cache.get(url);
if (Date.now() - timestamp < ttl) return dados;
}
const dados = await fetch(url).then(r => r.json());
cache.set(url, { dados, timestamp: Date.now() });
return dados;
}
// ✅ 3. Sempre mostre loading e trate erros visualmente
// ✅ 4. Use Promise.all para requisições independentes
// ✅ 5. Documente quais APIs você usa e seus termos de uso
// ✅ 6. Prepare um fallback para quando a API estiver fora
async function comFallback(fn, fallback) {
try {
return await fn();
} catch {
return fallback;
}
}
Tarefa para você
Usando as APIs deste artigo, construa um cartão de perfil de desenvolvedor:
Dados a exibir:
1. Avatar gerado pelo DiceBear com o nome do usuário
2. Campo de CEP com preenchimento automático via ViaCEP
3. Temperatura atual da capital do estado detectado via Open-Meteo
4. Um campo de username do GitHub que busca os repositórios do usuário
5. Exibir os 3 repos com mais estrelas
Extras:
- Skeleton loading em todos os campos
- Tratamento de erro em cada seção independentemente
(se o GitHub falhar, o resto ainda funciona)
- Botão "Gerar PDF" que exporta o cartão como CSV
com nome, cidade, estado e repos
Conclusão
Neste artigo você aprendeu:
- Como consumir a ViaCEP para preenchimento automático de endereços
- Como usar a API do IBGE para dados geográficos brasileiros
- Como buscar previsão do tempo com Open-Meteo sem precisar de chave
- Como construir um Pokédex com PokeAPI incluindo cache e comparação
- Como gerar avatares dinâmicos com DiceBear
- Como combinar múltiplas APIs em uma única aplicação
- Boas práticas de cache, debounce e tratamento de erros com APIs reais
No próximo artigo encerramos o Módulo 3 com uma revisão completa e um mini projeto que une tudo que aprendemos sobre JavaScript assíncrono e APIs.
📚 Fontes e Referências
- ViaCEP — Documentação: https://viacep.com.br
- IBGE API — Documentação: https://servicodados.ibge.gov.br/api/docs
- Open-Meteo — Documentação: https://open-meteo.com/en/docs
- PokeAPI — Documentação: https://pokeapi.co/docs/v2
- DiceBear — Documentação: https://www.dicebear.com/introduction
- JSONPlaceholder — Documentação: https://jsonplaceholder.typicode.com
- MDN Web Docs — Using Fetch: https://developer.mozilla.org/pt-BR/docs/Web/API/Fetch_API/Using_Fetch
- You Don't Know JS: Async & Performance — Kyle Simpson: https://github.com/getify/You-Dont-Know-JS