Weather App
Real API integration. Fetch live data, handle errors gracefully, and build dynamic UI.
API Setup
Use OpenWeatherMap's free tier — 1000 calls/day.
// 1. Sign up at openweathermap.org/api
// 2. Get your free API key
// 3. Store it securely (never commit to git!)
// Project structure:
// weather-app/
// ├── index.html
// ├── style.css
// ├── app.js
// └── config.js ← API key (gitignored)
// config.js:
const CONFIG = {
API_KEY: "your_api_key_here",
BASE_URL: "https://api.openweathermap.org/data/2.5",
};
// .gitignore:
// config.js
// API endpoints we'll use:
// Current weather: /weather?q={city}&appid={key}&units=metric
// 5-day forecast: /forecast?q={city}&appid={key}&units=metric
// By coordinates: /weather?lat={lat}&lon={lon}&appid={key}
// HTML structure:
// <div class="app">
// <form class="search">
// <input type="text" placeholder="Search city...">
// <button type="submit">🔍</button>
// </form>
// <button class="location-btn">📍 My Location</button>
// <div class="weather-display hidden">...</div>
// <div class="error hidden"></div>
// <div class="loading hidden">Loading...</div>
// </div>Fetching Data
async/await with proper error handling for API calls.
// Core fetch function with error handling:
async function fetchWeather(city) {
showLoading();
hideError();
try {
const url = `${CONFIG.BASE_URL}/weather?q=${encodeURIComponent(city)}&appid=${CONFIG.API_KEY}&units=metric`;
const response = await fetch(url);
if (!response.ok) {
if (response.status === 404) throw new Error("City not found");
if (response.status === 401) throw new Error("Invalid API key");
throw new Error("Failed to fetch weather data");
}
const data = await response.json();
displayWeather(data);
} catch (error) {
showError(error.message);
} finally {
hideLoading();
}
}
// Fetch 5-day forecast:
async function fetchForecast(city) {
const url = `${CONFIG.BASE_URL}/forecast?q=${encodeURIComponent(city)}&appid=${CONFIG.API_KEY}&units=metric`;
const response = await fetch(url);
if (!response.ok) throw new Error("Forecast unavailable");
return response.json();
}Always encode user input in URLs (encodeURIComponent). Check response.ok before parsing. Use finally for cleanup.
1 / 2
Geolocation
Get weather for the user's current location.
// Geolocation API — get user's coordinates:
const locationBtn = document.querySelector(".location-btn");
locationBtn.addEventListener("click", () => {
if (!navigator.geolocation) {
showError("Geolocation not supported in this browser");
return;
}
showLoading();
navigator.geolocation.getCurrentPosition(
// Success:
async (position) => {
const { latitude, longitude } = position.coords;
await fetchWeatherByCoords(latitude, longitude);
},
// Error:
(error) => {
hideLoading();
switch (error.code) {
case error.PERMISSION_DENIED:
showError("Location permission denied");
break;
case error.POSITION_UNAVAILABLE:
showError("Location unavailable");
break;
case error.TIMEOUT:
showError("Location request timed out");
break;
}
},
// Options:
{ enableHighAccuracy: false, timeout: 10000, maximumAge: 300000 }
);
});
async function fetchWeatherByCoords(lat, lon) {
try {
const url = `${CONFIG.BASE_URL}/weather?lat=${lat}&lon=${lon}&appid=${CONFIG.API_KEY}&units=metric`;
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch weather");
const data = await response.json();
displayWeather(data);
input.value = data.name; // show city name in search
} catch (error) {
showError(error.message);
} finally {
hideLoading();
}
}Dynamic UI
Render weather data with icons, colors, and animations.
// Transform API response into display:
function displayWeather(data) {
const weatherDisplay = document.querySelector(".weather-display");
weatherDisplay.classList.remove("hidden");
const { name, main, weather, wind, sys } = data;
const icon = weather[0].icon;
const iconUrl = `https://openweathermap.org/img/wn/${icon}@2x.png`;
weatherDisplay.innerHTML = `
<div class="weather-card">
<div class="location">
<h2>${escapeHtml(name)}, ${sys.country}</h2>
<p class="date">${formatDate(new Date())}</p>
</div>
<div class="temperature">
<img src="${iconUrl}" alt="${weather[0].description}">
<span class="temp">${Math.round(main.temp)}°C</span>
</div>
<p class="description">${weather[0].description}</p>
<div class="details">
<div class="detail">
<span class="label">Feels like</span>
<span class="value">${Math.round(main.feels_like)}°C</span>
</div>
<div class="detail">
<span class="label">Humidity</span>
<span class="value">${main.humidity}%</span>
</div>
<div class="detail">
<span class="label">Wind</span>
<span class="value">${Math.round(wind.speed)} m/s</span>
</div>
</div>
</div>
`;
// Dynamic background based on weather:
updateBackground(weather[0].main);
}
function updateBackground(condition) {
const app = document.querySelector(".app");
const backgrounds = {
Clear: "linear-gradient(135deg, #f6d365, #fda085)",
Clouds: "linear-gradient(135deg, #bdc3c7, #2c3e50)",
Rain: "linear-gradient(135deg, #667db6, #0082c8)",
Snow: "linear-gradient(135deg, #e6dada, #274046)",
Thunderstorm: "linear-gradient(135deg, #0f0c29, #302b63)",
};
app.style.background = backgrounds[condition] || backgrounds.Clear;
}
function formatDate(date) {
return date.toLocaleDateString("en-US", {
weekday: "long", month: "short", day: "numeric",
});
}Error Handling
Graceful failures — never show a broken UI.
// UI state helpers:
const loadingEl = document.querySelector(".loading");
const errorEl = document.querySelector(".error");
const weatherDisplay = document.querySelector(".weather-display");
function showLoading() {
loadingEl.classList.remove("hidden");
weatherDisplay.classList.add("hidden");
errorEl.classList.add("hidden");
}
function hideLoading() {
loadingEl.classList.add("hidden");
}
function showError(message) {
errorEl.textContent = message;
errorEl.classList.remove("hidden");
weatherDisplay.classList.add("hidden");
}
function hideError() {
errorEl.classList.add("hidden");
}
// Network resilience — retry with backoff:
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (response.ok) return response;
if (response.status >= 400 && response.status < 500) {
throw new Error("Client error"); // don't retry 4xx
}
} catch (error) {
if (i === retries - 1) throw error;
// Exponential backoff: 1s, 2s, 4s
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
}
}
}
// Cache recent searches (avoid redundant API calls):
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async function getCachedWeather(city) {
const key = city.toLowerCase();
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const data = await fetchWeatherRaw(city);
cache.set(key, { data, timestamp: Date.now() });
return data;
}
// Key patterns in this project:
// ✓ Real API integration with error handling
// ✓ Loading/error/success UI states
// ✓ Geolocation API
// ✓ Caching to reduce API calls
// ✓ Retry with exponential backoff
// ✓ Dynamic UI based on data
// ✓ Input sanitization (encodeURIComponent, escapeHtml)FAQ
Common questions about this project.