By the end of this lesson, you will:
Now that you've mastered GET requests, JSON parsing, API keys, and POST requests, it's time to build something practical and useful - a weather widget! Weather APIs are among the most popular APIs for beginners because they provide real, constantly changing data that everyone can relate to.
In this lesson, we'll use the OpenWeatherMap API to create a weather widget that shows current conditions and a 5-day forecast. This will demonstrate how to work with a real-world API that millions of applications use daily.
OpenWeatherMap provides several endpoints, but we'll focus on two main ones:
// Current weather endpoint
const currentWeatherURL = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`
// 5-day forecast endpoint
const forecastURL = `https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${apiKey}&units=metric`
Notice the units=metric
parameter - this gives us temperature in Celsius instead of Kelvin.
Let's look at what a weather API response looks like:
{
"name": "London",
"main": {
"temp": 15.5,
"feels_like": 14.8,
"humidity": 72,
"pressure": 1013
},
"weather": [
{
"main": "Clouds",
"description": "broken clouds",
"icon": "04d"
}
],
"wind": {
"speed": 3.2,
"deg": 240
}
}
This nested structure contains all the weather information we need!
<!DOCTYPE html>
<html>
<head>
<title>Weather Widget</title>
<style>
.weather-widget {
max-width: 400px;
margin: 20px auto;
padding: 20px;
border-radius: 10px;
background: linear-gradient(135deg, #74b9ff, #0984e3);
color: white;
font-family: Arial, sans-serif;
}
.current-weather {
text-align: center;
margin-bottom: 20px;
}
.temperature {
font-size: 48px;
font-weight: bold;
}
.forecast {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
.forecast-day {
text-align: center;
padding: 10px;
background: rgba(255,255,255,0.2);
border-radius: 5px;
margin: 0 2px;
}
</style>
</head>
<body>
<div class="weather-widget">
<input type="text" id="cityInput" placeholder="Enter city name" />
<button onclick="getWeather()">Get Weather</button>
<div class="current-weather" id="currentWeather">
<!-- Current weather will be displayed here -->
</div>
<div class="forecast" id="forecast">
<!-- 5-day forecast will be displayed here -->
</div>
</div>
</body>
</html>
const API_KEY = 'your_openweathermap_api_key_here'
async function getCurrentWeather(city) {
try {
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Weather data not found for ${city}`)
}
const weatherData = await response.json()
return weatherData
} catch (error) {
console.error('Error fetching weather:', error)
throw error
}
}
function displayCurrentWeather(weatherData) {
const currentWeatherDiv = document.getElementById('currentWeather')
// Extract data from the JSON response
const cityName = weatherData.name
const temperature = Math.round(weatherData.main.temp)
const description = weatherData.weather[0].description
const humidity = weatherData.main.humidity
const windSpeed = weatherData.wind.speed
const icon = weatherData.weather[0].icon
// Create the HTML content
currentWeatherDiv.innerHTML = `
<h2>${cityName}</h2>
<img src="https://openweathermap.org/img/w/${icon}.png" alt="${description}">
<div class="temperature">${temperature}°C</div>
<div class="description">${description}</div>
<div class="details">
<p>Humidity: ${humidity}%</p>
<p>Wind: ${windSpeed} m/s</p>
</div>
`
}
async function getForecast(city) {
try {
const url = `https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${API_KEY}&units=metric`
const response = await fetch(url)
const forecastData = await response.json()
// The forecast API returns data every 3 hours
// Let's get one forecast per day (every 8th item = 24 hours)
const dailyForecasts = []
for (let i = 0; i < forecastData.list.length; i += 8) {
dailyForecasts.push(forecastData.list[i])
}
return dailyForecasts.slice(0, 5) // Get 5 days
} catch (error) {
console.error('Error fetching forecast:', error)
throw error
}
}
function displayForecast(forecastData) {
const forecastDiv = document.getElementById('forecast')
const forecastHTML = forecastData.map(day => {
const date = new Date(day.dt * 1000) // Convert Unix timestamp
const dayName = date.toLocaleDateString('en', { weekday: 'short' })
const temp = Math.round(day.main.temp)
const icon = day.weather[0].icon
return `
<div class="forecast-day">
<div>${dayName}</div>
<img src="https://openweathermap.org/img/w/${icon}.png" alt="weather">
<div>${temp}°C</div>
</div>
`
}).join('')
forecastDiv.innerHTML = forecastHTML
}
let isMetric = true // Track current unit
function convertTemperature(celsius) {
if (isMetric) {
return `${Math.round(celsius)}°C`
} else {
const fahrenheit = (celsius * 9/5) + 32
return `${Math.round(fahrenheit)}°F`
}
}
function toggleUnits() {
isMetric = !isMetric
// Re-display weather with new units
if (window.currentWeatherData) {
displayCurrentWeather(window.currentWeatherData)
}
if (window.forecastData) {
displayForecast(window.forecastData)
}
}
// Add this button to your HTML
// <button onclick="toggleUnits()">Switch to °F/°C</button>
async function getWeather() {
const cityInput = document.getElementById('cityInput')
const city = cityInput.value.trim()
if (!city) {
alert('Please enter a city name')
return
}
try {
// Show loading message
document.getElementById('currentWeather').innerHTML = '<p>Loading...</p>'
document.getElementById('forecast').innerHTML = '<p>Loading forecast...</p>'
// Fetch both current weather and forecast
const [currentWeather, forecast] = await Promise.all([
getCurrentWeather(city),
getForecast(city)
])
// Store data globally for unit conversion
window.currentWeatherData = currentWeather
window.forecastData = forecast
// Display the weather data
displayCurrentWeather(currentWeather)
displayForecast(forecast)
} catch (error) {
document.getElementById('currentWeather').innerHTML =
`<p>Error: ${error.message}</p>`
document.getElementById('forecast').innerHTML = ''
}
}
Weather APIs return various condition codes. Here's how to handle them:
function getWeatherEmoji(weatherMain) {
const weatherEmojis = {
'Clear': '☀️',
'Clouds': '☁️',
'Rain': '🌧️',
'Drizzle': '🌦️',
'Thunderstorm': '⛈️',
'Snow': '❄️',
'Mist': '🌫️',
'Fog': '🌫️'
}
return weatherEmojis[weatherMain] || '🌤️'
}
// Use in your display function:
const emoji = getWeatherEmoji(weatherData.weather[0].main)
function getWeatherBackground(weatherMain) {
const backgrounds = {
'Clear': 'linear-gradient(135deg, #74b9ff, #0984e3)',
'Clouds': 'linear-gradient(135deg, #636e72, #2d3436)',
'Rain': 'linear-gradient(135deg, #74b9ff, #0984e3)',
'Snow': 'linear-gradient(135deg, #ddd, #74b9ff)'
}
return backgrounds[weatherMain] || backgrounds['Clear']
}
// Apply to your widget:
document.querySelector('.weather-widget').style.background =
getWeatherBackground(weatherData.weather[0].main)
Extend your widget to show:
Use the Geolocation API to automatically get weather for the user's current location:
function getCurrentLocationWeather() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(async (position) => {
const lat = position.coords.latitude
const lon = position.coords.longitude
const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}&units=metric`
const response = await fetch(url)
const weatherData = await response.json()
displayCurrentWeather(weatherData)
})
}
}
Add a feature that highlights extreme weather conditions:
function checkWeatherAlerts(weatherData) {
const temp = weatherData.main.temp
const condition = weatherData.weather[0].main
let alert = ''
if (temp > 35) alert = '🔥 Very Hot!'
if (temp < -10) alert = '🥶 Very Cold!'
if (condition === 'Thunderstorm') alert = '⚠️ Storm Warning!'
return alert
}
async function robustWeatherFetch(city) {
try {
const response = await fetch(url)
// Check if city was found
if (response.status === 404) {
throw new Error('City not found. Please check the spelling.')
}
// Check for other API errors
if (!response.ok) {
throw new Error(`Weather service error: ${response.status}`)
}
return await response.json()
} catch (error) {
if (error.name === 'TypeError') {
throw new Error('Network error. Please check your connection.')
}
throw error
}
}
let lastRequestTime = 0
const MIN_REQUEST_INTERVAL = 1000 // 1 second between requests
async function rateLimitedRequest(url) {
const now = Date.now()
const timeSinceLastRequest = now - lastRequestTime
if (timeSinceLastRequest < MIN_REQUEST_INTERVAL) {
await new Promise(resolve =>
setTimeout(resolve, MIN_REQUEST_INTERVAL - timeSinceLastRequest)
)
}
lastRequestTime = Date.now()
return fetch(url)
}
In this lesson, you've built a complete weather widget that:
You've now seen how APIs can provide dynamic, real-world data that makes your applications truly useful and engaging.
In Concept 06: Error Handling and Status Codes, we'll dive deeper into handling API errors gracefully, understanding HTTP status codes, and building robust applications that work even when things go wrong. You'll learn how to create better user experiences by properly managing network failures, API rate limits, and invalid responses.