JavaScript Weather API Exercise: Build a Forecast App with Fetch
This beginner JavaScript tutorial shows how to build a small weather forecast app with WeatherAPI.com, fetch(), form values, JSON data, and DOM rendering.
This is a learning exercise, so the first version uses a direct request from frontend JavaScript.
You will practice:
- creating a simple weather search form
- making a direct
fetch()request to WeatherAPI.com - rendering weather data on the page
- handling loading and error states
- adding attribution for WeatherAPI.com
Main idea
User enters city
-> JavaScript sends request to WeatherAPI.com
-> WeatherAPI.com returns JSON
-> JavaScript shows weather on the page
1. Can WeatherAPI.com be used for an exercise?
Yes, WeatherAPI.com can be used for a learning or portfolio exercise if you follow their rules.
At the time these notes were checked, the Free plan included:
$0per month100KAPI calls per month- realtime weather
- 3-day forecast/future weather
- commercial and non-commercial use
WeatherAPI terms also say that Free API users must credit WeatherAPI.com as the source of the data.
Simple answer
Legal for a practice project: yes, if you follow the terms
Free: yes, if you stay inside the Free plan limits
Required: credit WeatherAPI.com as the data source
2. Direct frontend request
For this exercise, put the API key directly in your JavaScript code.
This is the simplest way to understand how API requests work.
It helps you check that:
- your API key works
- the API URL is correct
- the city parameter is correct
- you understand the response data
Example direct request:
fetch("https://api.weatherapi.com/v1/current.json?key=YOUR_KEY&q=Berlin")
For a learning project, this is okay.
In a bigger future project, you can improve this by moving the API key to a backend.
3. Exercise 1: Build the HTML
Create a simple weather search form.
<form class="weather-form">
<label>
City
<input class="weather-input" type="text" name="city" placeholder="Berlin" required>
</label>
<button type="submit">Search</button>
</form>
<p class="weather-status"></p>
<div class="weather-result"></div>
<p>
Weather data by
<a href="https://www.weatherapi.com/" target="_blank" rel="noopener noreferrer">
WeatherAPI.com
</a>
</p>
The attribution link is important for the Free plan.
4. Exercise 2: Get form values
Before making the weather request, first get the values from the form.
When the form is submitted, the browser gives the handler function an event object.
This object contains information about what happened: which event happened, which element started it, and which element is handling it.
form.addEventListener("submit", handlerSubmit);
function handlerSubmit(event) {
event.preventDefault();
console.log(event);
const city = event.currentTarget.elements.city.value;
const days = event.currentTarget.elements.days.value;
console.log("City:", city);
console.log("Days:", days);
}
If you open DevTools and look in the Console, you can inspect this event object.
In DevTools, open the logged event object and look for properties like target, currentTarget, and elements.
target is the element where the event started. For example, it can be the button or input that was used.
currentTarget is the element where the event listener is attached.
In this example, the listener is attached to the form:
form.addEventListener("submit", handlerSubmit);
So inside handlerSubmit, event.currentTarget is the form.
A form has an elements collection. It contains all form controls inside the form, such as inputs and selects.
The important detail is that the HTML name attribute becomes a key inside elements.
Because the input has name="city", the form creates an elements.city key:
<input name="city">
event.currentTarget.elements.city
Because the select has name="days", the form creates an elements.days key:
<select name="days">
event.currentTarget.elements.days
Then .value gives the value that the user typed or selected.
Here city and days already store the final values because .value is written at the end of each line.
const city = event.currentTarget.elements.city.value;
const days = event.currentTarget.elements.days.value;
For example, if the user types Berlin and selects 3 days, then:
city = "Berlin"
days = "3"
Because the variables already contain the values, log them directly:
console.log("City:", city);
console.log("Days:", days);
Another possible style is to first get the elements, and then read .value later.
const city = event.currentTarget.elements.city;
const days = event.currentTarget.elements.days;
console.log("City:", city.value);
console.log("Days:", days.value);
Both ways work. The important idea is:
element = input or select element
element.value = actual user value
After you understand these values, you can use cityValue and daysValue in the WeatherAPI request.
5. Exercise 3: Make the API request
Put your API key in a variable and use it inside the request URL.
const form = document.querySelector(".weather-form");
const input = document.querySelector(".weather-input");
const statusText = document.querySelector(".weather-status");
const result = document.querySelector(".weather-result");
const API_KEY = "YOUR_WEATHER_API_KEY";
form.addEventListener("submit", event => {
event.preventDefault();
const city = input.value.trim();
if (!city) {
statusText.textContent = "Please enter a city.";
return;
}
statusText.textContent = "Loading weather...";
result.textContent = "";
const url = `https://api.weatherapi.com/v1/current.json?key=${API_KEY}&q=${encodeURIComponent(city)}`;
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error("Weather request failed");
}
return response.json();
})
.then(weather => {
statusText.textContent = "";
renderWeather({
city: weather.location.name,
country: weather.location.country,
tempC: weather.current.temp_c,
condition: weather.current.condition.text,
});
})
.catch(error => {
statusText.textContent = "Could not load weather. Try again later.";
console.log(error);
});
});
After this works, open DevTools and look at the Network tab. You should see the request and the JSON response.
6. Exercise 4: Render safely
When showing API data, use textContent for values.
This avoids inserting unexpected HTML into the page.
Unexpected HTML means text that the browser reads as real HTML code instead of normal text.
For example, imagine an API or user input returns a city name like this:
<img src="x" onerror="alert('Bad code')">
If you insert that value with innerHTML, the browser may create a real <img> element and run the JavaScript inside the attribute.
This kind of attack is called XSS, which means Cross-Site Scripting.
XSS is dangerous because bad code can run inside the user's browser.
It can be used to:
- show fake content on the page
- steal data from the page
- send requests as the user
- break the layout or behavior of the website
Dangerous way
result.innerHTML = weather.city;
Here the browser treats the value as HTML.
Safer way
title.textContent = weather.city;
Here the browser treats the value as text only. Even if the value looks like HTML, it is displayed as text and not executed as code.
function renderWeather(weather) {
// Render using innerHTML (example)
result.innerHTML = `
${weather.city}, ${weather.country}
Temperature: ${weather.tempC} C
Condition: ${weather.condition}
`;
}
The page still uses innerHTML only to clear the container. The user-visible API values are inserted with textContent.
7. Common mistakes
- Writing the API URL incorrectly.
- Forgetting
key=orq=in the query string. - Forgetting to use
encodeURIComponent(city). - Forgetting to credit WeatherAPI.com when using the Free plan.
- Not handling request errors.
- Not handling empty city input.
- Using
innerHTMLfor city names or other API values. - Not checking the current Free plan limits before publishing.
8. Future improvement
In this exercise, the API key is visible in frontend JavaScript.
That is acceptable for a simple learning project.
Later, when you want to make the project more professional, you can move the key to a backend.
Future structure
Frontend
-> asks your backend for weather
Backend
-> keeps API key private
-> asks WeatherAPI.com
-> sends weather data back to frontend
For now, focus on understanding the API request and rendering the response.
9. Final checklist
Create form
Read city from input
Build WeatherAPI URL
Add API key
Use encodeURIComponent(city)
Fetch weather data
Check response.ok
Convert response to JSON
Render weather with textContent
Show loading and error messages
Credit WeatherAPI.com
Watch the monthly request limit
10. Practice: Write the HTML, CSS, and JavaScript
Use this part to write the full weather app code yourself.
Start with the HTML structure, then add CSS styles, and finish with JavaScript logic.
HTML
<!-- Write your HTML code here -->
CSS
/* Write your CSS code here */
JavaScript
// Write your JavaScript code here
11. Quick summary
WeatherAPI.com is a good API for practicing backend interaction.
For this exercise, use a direct frontend request so the idea stays simple.
The most important flow is:
City input
-> fetch WeatherAPI URL
-> receive JSON
-> render result on the page
A backend version can be added later when the project becomes more advanced.
12. Live practice app: how this code works
This is the working version of the weather forecast exercise. The form below is connected to JavaScript. When a user types a city, chooses the number of forecast days, and clicks Show forecast, JavaScript reads the form values, sends a request to WeatherAPI.com, receives forecast JSON data, and creates one weather forecast card for each day.
Step 1: The HTML form gives JavaScript useful names
The city input has name="city". This is important because the JavaScript can later find this input through the form:
event.currentTarget.elements.city
The days select has name="days", so JavaScript can find it in the same way:
event.currentTarget.elements.days
Then .value gives the real user value. For example, if the user types Berlin and chooses 3 days, JavaScript receives:
cityValue = "Berlin"
daysValue = "3"
Step 2: The form submit starts the JavaScript
The submit listener connects the form to the function that controls the exercise:
form.addEventListener("submit", handlerSubmit);
This means: when the form is submitted, run handlerSubmit. Inside that function, event.preventDefault() stops the browser from reloading the page. Without it, the page would refresh and the JavaScript result would disappear.
Step 3: Read form values without destructuring
For beginners, the clearest way is to read each value step by step:
const cityValue = event.currentTarget.elements.city.value;
const daysValue = event.currentTarget.elements.days.value;
A longer version without destructuring would be:
const cityInput = event.currentTarget.elements.city;
const daysSelect = event.currentTarget.elements.days;
const cityValue = cityInput.value;
const daysValue = daysSelect.value;
Both versions do the same thing. The longer version shows that cityInput and daysSelect are HTML elements, and .value is the actual user data.
Step 4: Build the WeatherAPI request
The API address has two parts. BASE_URL stores the part that stays the same, and /forecast.json tells WeatherAPI that we want forecast data.
const BASE_URL = "https://api.weatherapi.com/v1";
The request also needs query parameters: the API key, the city, the number of days, and the language. URLSearchParams helps build that query string correctly.
const params = new URLSearchParams({
key: API_KEY,
q: city,
days: days,
lang: "en"
});
The final request looks like this idea:
https://api.weatherapi.com/v1/forecast.json?key=...&q=Berlin&days=3&lang=en
Step 5: Fetch returns a Promise
fetch() starts the request, but the answer does not arrive immediately. That is why the code uses .then() and .catch().
serviceWeather(cityValue, daysValue)
.then(data => {
list.innerHTML = createMarkup(data.forecast.forecastday);
})
.catch(error => {
console.log(error);
});
.then() runs when the API request succeeds. .catch() runs when something goes wrong, for example a wrong city, wrong API key, network problem, or API limit problem.
Step 6: Render weather cards without destructuring
WeatherAPI returns forecast days inside data.forecast.forecastday. This is an array, so map() can loop over it and create one card for each day.
The beginner-friendly version reads nested values one line at a time:
const date = weatherDay.date;
const avgtemp_c = weatherDay.day.avgtemp_c;
const text = weatherDay.day.condition.text;
const icon = weatherDay.day.condition.icon;
This is easier to understand than destructuring because you can see the full path to every value. For example, weatherDay.day.condition.text means: start with one forecast day, go into day, then into condition, then take text.
Try it
Use the form below to request a weather forecast for a city. Choose 1, 2, or 3 days, then the result area will show daily forecast cards with the date, weather condition, icon, and average temperature.
Open DevTools Console while testing. The code logs the submit event, the city value, the days value, and the API response so you can connect what you see on the page with what JavaScript receives.
Forecast results will appear below after you submit the form.