Lesson 5 of 6

Live weather

Bring live data into your app. You'll call a free weather service, turn its answer into a forecast card, and remember the last city and the °F/°C choice.

15 minvos.netnetwork permission

What you'll build

Quick Weather: search any city and see the current temperature and a 5-day forecast, using the free Open-Meteo service (no sign-up or key).

You'll need

Lessons 1 and 2.

You'll learn

  • How to list the websites your app talks to
  • How to call an API with vos.net.fetch
  • How to check the response and handle failures
  • How to show data from the internet safely
Peek at the end result first, or build it step by step below.

Why vos.net instead of fetch?

Apps run in a protected window that can't talk to the internet directly. vos.net.fetch sends the request for you, only to websites your app declared, and without the browser errors (CORS) you'd often hit with plain fetch. People see the list of websites when they install your app.

Step 1 — List the websites

1

Add both Open-Meteo addresses

In the Info tab, under "Allowed network origins", add one per line:

text
https://geocoding-api.open-meteo.com/
https://api.open-meteo.com/

Each line is a starting address: anything that begins with it is allowed. Only https:// addresses work, up to 32 of them.

Try it in the Console

javascript
const res = await window.vos.net.fetch("https://geocoding-api.open-meteo.com/v1/search?count=1&name=Paris")
res.status          // 200
JSON.parse(res.body).results[0].country   // "France"

Step 2 — Fetch JSON

2

A small helper you can reuse

vos.net.fetch gives you back { status, headers, body }. It succeeds for every reply, even "404 Not found", so check status yourself. The body is text; parse it.

javascript
// vos.net.fetch resolves for every HTTP status, so check it yourself.
// The response body is text: parse JSON with JSON.parse.
function getJSON(url) {
  return window.vos.net.fetch(url).then(function (res) {
    if (res.status !== 200) throw new Error("The weather service replied " + res.status);
    return JSON.parse(res.body);
  });
}

Step 3 — From a city name to a forecast

3

Two calls in a row

First turn the city name into map coordinates, then ask for the forecast at those coordinates. Any failure along the way (no internet, unknown city, service down) lands in one catch and shows a message.

javascript
function lookup(city) {
  $("error").hidden = true;
  $("status").textContent = "Looking up " + city + "…";
  var geo = "https://geocoding-api.open-meteo.com/v1/search?count=1&name=" + encodeURIComponent(city);
  return getJSON(geo).then(function (data) {
    var place = data.results && data.results[0];
    if (!place) throw new Error("No place called \"" + city + "\"");
    var url = "https://api.open-meteo.com/v1/forecast"
      + "?latitude=" + place.latitude + "&longitude=" + place.longitude
      + "&current=temperature_2m,weather_code"
      + "&daily=weather_code,temperature_2m_max,temperature_2m_min"
      + "&forecast_days=5&timezone=auto&temperature_unit=" + settings.unit;
    return getJSON(url).then(function (forecast) { show(place, forecast); });
  }).then(function () {
    settings.city = city;
    saveSettings();
    $("status").textContent = "Updated " + new Date().toLocaleTimeString();
  }).catch(function (err) {
    $("error").textContent = err.message;
    $("error").hidden = false;
    $("status").textContent = "";
  });
}
Checkpoint: search "Tokyo". The status says "Updated" with the time.

Step 4 — Show it safely

4

Build the card

Numbers from the forecast are safe to place in HTML. The place name is text from the internet, so it's set with textContent, the same rule as user text in Lesson 2.

javascript
function show(place, f) {
  var days = f.daily.time.map(function (d, i) {
    var name = new Date(d + "T12:00").toLocaleDateString(undefined, { weekday: "short" });
    return '<div class="day">' + name + '<div>' + icon(f.daily.weather_code[i]) + '</div>'
      + Math.round(f.daily.temperature_2m_max[i]) + '° <span class="muted">'
      + Math.round(f.daily.temperature_2m_min[i]) + '°</span></div>';
  }).join("");
  $("result").innerHTML = '<div class="card">'
    + '<div class="place"></div>'
    + '<div class="now"><span class="icon">' + icon(f.current.weather_code) + '</span>'
    + '<span class="temp">' + Math.round(f.current.temperature_2m) + '°</span></div>'
    + '<div class="days">' + days + '</div></div>';
  // Place names come from the internet: set them as text, not HTML.
  $("result").querySelector(".place").textContent =
    place.name + (place.country ? ", " + place.country : "");
}
Checkpoint: a blue card shows the temperature and five days. Search "Nowhereville123" and you get a friendly "No place called…" message instead of a broken screen.

Practice

See a blocked request

In the Console, try await window.vos.net.fetch("https://example.com"). It's refused because the address isn't in your list. That's the protection people get when they install your app.

Add wind speed

Show the current wind speed under the temperature.

Add wind_speed_10m to the current= list in the forecast URL, then show f.current.wind_speed_10m.

Refresh every 15 minutes

Keep the forecast fresh while the app stays open.
Show a solution
javascript
setInterval(function () { lookup(settings.city); }, 15 * 60 * 1000);

Recap

  • Declare every website your app calls; only https addresses are allowed.
  • vos.net.fetch resolves for every status, so check status before using the body.
  • Parse the text body with JSON.parse.
  • Treat anything from the internet like user text: set it with textContent.
Up next · Lesson 6Publish your appSave, submit for review and ship updates to everyone.

Limits like timeouts and response size are in the vos.net reference.