← Back

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 learning exercise sends the browser request through a small backend proxy so the WeatherAPI key stays private on the server.

You will practice:

  1. creating a simple weather search form
  2. making a fetch() request to a same-origin backend endpoint
  3. rendering weather data on the page
  4. handling loading and error states
  5. adding attribution for WeatherAPI.com

Main idea

User enters city
-> JavaScript requests /api/weather
-> backend safely asks WeatherAPI.com
-> backend returns selected forecast 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:

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. Keep the API key on the backend

Do not put a real API key in public frontend JavaScript. Visitors can inspect downloaded scripts, copy the key and consume its request allowance.

The browser should call a same-origin endpoint that validates the request and uses the secret key on the server.

This design helps you check that:

Example browser request:

fetch("/api/weather?city=Berlin&days=1")

The backend adds the private key when it communicates with WeatherAPI.com.

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

Build a same-origin request containing only the user-selected city and forecast length.

const form = document.querySelector(".weather-form");
const input = document.querySelector(".weather-input");
const statusText = document.querySelector(".weather-status");
const result = document.querySelector(".weather-result");

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 params = new URLSearchParams({ city, days: "1" });
  const url = `/api/weather?${params}`;

  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:

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

8. Protect the external service

The API key is stored only in the backend environment. The backend validates inputs, limits repeated requests and returns only the forecast fields the interface needs.

This is useful even for a learning project because publishing a real secret in frontend JavaScript makes it available to everyone.

Future structure

Frontend
-> asks your backend for weather

Backend
-> keeps API key private
-> asks WeatherAPI.com
-> sends weather data back to frontend

The frontend can therefore focus on form handling, request states and rendering the response.

9. Final checklist

Create form
Read city from input
Build URLSearchParams with city and days
Request the same-origin backend endpoint
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 the same-origin backend endpoint so the external API key remains private.

The most important flow is:

City input
-> fetch /api/weather
-> receive validated forecast 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 backend request

The browser calls the same-origin /api/weather endpoint. It sends only the city and number of days; the private WeatherAPI key remains on the server.

URLSearchParams builds the query string correctly.

const params = new URLSearchParams({
  city: city,
  days: days,
});

The final request looks like this idea:

/api/weather?city=Berlin&days=3

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 an unknown city, network problem or temporary service limit.

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.

← Back