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.
What you'll build
You'll need
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
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
Add both Open-Meteo addresses
In the Info tab, under "Allowed network origins", add one per line:
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
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
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.
// 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
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.
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
+ "¤t=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 = "";
});
}Step 4 — Show it safely
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.
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 : "");
}Practice
See a blocked request
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
Add wind_speed_10m to the current= list in the forecast URL, then show f.current.wind_speed_10m.
Refresh every 15 minutes
Show a solution
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.
Limits like timeouts and response size are in the vos.net reference.