Loading...
Loading...
Real API integration. Fetch live data, handle errors gracefully, and build dynamic UI.
Module 11 · JavaScript Projects
Theory becomes skill through building. These guided projects — calculator, todo app, weather app, and more — combine everything you've learned into real applications you can extend, share, and put in a portfolio.
Learn Weather App in JavaScript — covering API Setup, Fetching Data, Geolocation, Dynamic UI with interactive visual examples and step-by-step explanations.
Reading about JavaScript is not the same as building with it. These guided projects combine variables, functions, DOM manipulation, async fetch, and state management into applications you can run, extend, and showcase.
Each project walks through architecture decisions, common pitfalls, and incremental steps. By the end you will have portfolio-ready apps and the confidence to start your own projects from scratch.
Understanding weather app is essential for every JavaScript developer. It shows up in frontend UI code, backend APIs, and framework internals — and getting it wrong leads to bugs that are hard to trace. This chapter builds intuition with visuals so the behavior sticks.
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>
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.
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(); } }
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", }); }
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)
Common questions about weather app.