Time to use fetch in a realistic interface. The weather data here comes from a local mock (a data: URL), so the project works anywhere and needs no API key. At the end you will see how to switch to a real API.
<style>
.weather { font-family: system-ui, sans-serif; max-width: 320px; }
select, button { font: inherit; padding: 8px; }
.card { margin-top: 12px; padding: 14px; border-radius: 10px; background: #f4f9fc; }
.temp { font-size: 44px; font-weight: 700; }
.error { background: #fdecea; color: #b00020; }
.row { display: flex; gap: 8px; }
</style>
<div class="weather">
<div class="row">
<label for="city" style="align-self:center">City</label>
<select id="city">
<option value="pune">Pune</option>
<option value="london">London</option>
<option value="atlantis">Atlantis (will fail)</option>
</select>
<button id="load">Show weather</button>
</div>
<div id="output" role="status" aria-live="polite"></div>
</div>
<script>
// ---- a pretend server: returns data, or a 404 for unknown cities ----
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
const DATA = {
pune: { city: "Pune", temperature: 31, condition: "Sunny", humidity: 48 },
london: { city: "London", temperature: 12, condition: "Cloudy", humidity: 81 }
};
async function fakeFetch(url) {
await wait(500); // network delay
const city = new URL(url, "https://example.test").searchParams.get("city");
return DATA[city]
? new Response(JSON.stringify(DATA[city]), { headers: { "Content-Type": "application/json" } })
: new Response("Not found", { status: 404 });
}
// ---- the real application code starts here ----
const output = document.querySelector("#output");
const button = document.querySelector("#load");
function show(html) { output.replaceChildren(html); }
function card(text, className = "") {
const div = document.createElement("div");
div.className = "card " + className;
div.textContent = text;
return div;
}
async function loadWeather(city) {
button.disabled = true;
show(card("Loading…"));
try {
const response = await fakeFetch("/weather?city=" + encodeURIComponent(city)); // fetch(...) with a real API
if (!response.ok) {
throw new Error(response.status === 404 ? "We could not find that city." : "The weather service is having trouble.");
}
const data = await response.json();
const wrapper = document.createElement("div");
wrapper.className = "card";
const heading = document.createElement("h3");
heading.textContent = data.city;
const temp = document.createElement("div");
temp.className = "temp";
temp.textContent = data.temperature + "°C";
const detail = document.createElement("p");
detail.textContent = `${data.condition}, humidity ${data.humidity}%`;
wrapper.append(heading, temp, detail);
show(wrapper);
} catch (error) {
show(card(error.message, "error"));
} finally {
button.disabled = false;
}
}
button.addEventListener("click", () => loadWeather(document.querySelector("#city").value));
</script>
Choose Atlantis to see the error state.
What this practises
async/awaitand atry/catch/finallyaround the request (async errors).response.okcheck, and different messages for 404 and server problems (HTTP errors).- Loading, success and error states, with the button disabled while loading (Loading States).
encodeURIComponentfor the value in the URL.- Safe output with
textContent.
Switching to a real API
Replace fakeFetch with fetch. Some weather services are free and need no key. Others require an account, and a key placed in front-end code is visible to everyone, so a service that needs a secret key should be called from your own server. Read the provider’s documentation and terms first.
// Example shape only: always follow the provider's own documentation.
const url = new URL("https://api.example-weather.test/v1/current");
url.searchParams.set("latitude", 18.52);
url.searchParams.set("longitude", 73.86);
const response = await fetch(url);
if (!response.ok) throw new Error("Weather service error " + response.status);
const data = await response.json();
Real APIs can fail, be slow, or block cross-origin requests (CORS), so keep the error handling you just wrote.
Try changing it
- Add a timeout with
AbortSignal.timeout()(AbortController). - Cache each city’s result for a minute in a
Map. - Add a Retry button to the error card.
- Show a 3-day forecast from an array in the data.