Skip to content

JavaScript Tutorial Practical Projects

Project 10: A Weather Interface with an API

Lesson 249 of 258 2 min read By Deepak Paswan Updated Oct 4, 2026

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.

index.html
HTML
<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/await and a try/catch/finally around the request (async errors).
  • response.ok check, and different messages for 404 and server problems (HTTP errors).
  • Loading, success and error states, with the button disabled while loading (Loading States).
  • encodeURIComponent for 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.

script.js
JS
// 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

  1. Add a timeout with AbortSignal.timeout() (AbortController).
  2. Cache each city’s result for a minute in a Map.
  3. Add a Retry button to the error card.
  4. Show a 3-day forecast from an array in the data.