EDUCATIONOverviewStudentsTeachersSchoolsCurriculumProject Library
GUIDED PROJECT·Beginner·~8 hours·10 min read

Build a Weather App

Build your first app powered by live data — a weather app that turns a city name into a real forecast. You'll learn to call an API, handle async code, read real documentation, and cover the unhappy paths (loading, errors, a city that doesn't exist) that separate a real app from a happy-path demo.

Open in Cartara Classroom →

WHAT YOU'LL WALK AWAY WITH
  • A deployed weather app that takes a city and shows its live forecast
  • A real grasp of async/await and why network calls can't be treated like ordinary code
  • The habit that outlasts any single API: reading the docs and shaping your code around the JSON you actually get back
  • Loading, empty, and error states handled on purpose — the difference between a demo and something you'd hand to a friend
TIER
Beginner
TIME
~8 hours
SKILLS
APIs, async code, reading docs
STACK
JavaScript, a public weather API
PORTFOLIO
Your first app powered by live data
REQUIRES
Calculator or Todo App

Almost every app you'll ever build does the same core thing: it asks another computer for data and shows it to a person. A weather app is the smallest honest version of that. Type "Tokyo," get Tokyo's forecast. Simple to describe, and it teaches the exact skills — fetch, async/await, reading real API docs, handling failure — that every bigger project reuses.

This assumes you've built something small before, like the Calculator or Todo App from the project library — you can write a function, change the page from JavaScript, and read an error in the console. That's enough.

Info

Estimated time: ~8 hours. The code is short. The time goes into the two things that are genuinely new: understanding why async code works the way it does, and learning to read an API's documentation to find what you need. Don't rush those — they're the whole point.

What you're building

A single page. An input for a city name, a search button, and a card that shows the current conditions once you search. No framework, no build step — just an index.html, a little CSS, and one JavaScript file. You'll be able to open it in a browser and, at the end, put it on the real internet with a URL you can share.

The data source: Open-Meteo, a free weather API that needs no sign-up and no API key. That keeps you unblocked and, importantly, keeps you out of a trap: an API key is a secret, and secrets don't belong in code that runs in a browser. Open-Meteo has nothing to hide, so it's perfect for a first client-side app.

Tip

Most APIs do require a key, and doing that safely means putting the key on a server the browser never sees — exactly what the AI Chatbot project teaches. For now, a keyless API lets you focus on one new thing at a time. That's a deliberate learning choice, not a shortcut.

Milestone 0 — The page and a plan

Create three files in a folder:

weather-app/
  index.html
  style.css
  app.js

A minimal index.html:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Weather</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <main>
      <h1>Weather</h1>
      <form id="search">
        <input id="city" placeholder="Enter a city…" />
        <button type="submit">Search</button>
      </form>
      <div id="result"></div>
    </main>
    <script src="app.js"></script>
  </body>
</html>

Open it in your browser. It does nothing yet — that's fine. Before writing any JavaScript, get the plan straight, because it explains every line that follows. To show weather for "Tokyo," you actually need two API calls:

  1. Geocoding — turn the name "Tokyo" into coordinates (latitude and longitude). Weather APIs speak in coordinates, not city names.
  2. Forecast — ask for the weather at those coordinates.

Understanding that upfront — that "search by city" is two steps, not one — is the difference between following a recipe and knowing what you're cooking.

Milestone 1 — Your first fetch

Before touching the page, prove you can talk to the API at all. In app.js:

async function getCoordinates(city) {
  const url =
    "https://geocoding-api.open-meteo.com/v1/search?name=" +
    encodeURIComponent(city) +
    "&count=1";
  const response = await fetch(url);
  const data = await response.json();
  console.log(data);
  return data;
}

getCoordinates("Tokyo");

Reload the page, open the browser console, and read what prints. This is the most important habit in the whole project: look at the real data before you write code that uses it. You'll see a results array with latitude, longitude, and name fields. You didn't guess that structure — you looked.

Now the part people memorize instead of understand. Slow down here:

  • fetch(url) doesn't return the data — it returns a promise. A network request takes time (tens or hundreds of milliseconds), and JavaScript refuses to freeze the whole page waiting. So fetch immediately hands you an IOU: "I'll have a response later."
  • await is you saying "I'll wait for that IOU before continuing." The line pauses until the response arrives, then gives you the real value. It only works inside an async function — that keyword is what makes await legal.
  • Why .json() needs its own await: the response arrives as a raw stream of text; parsing it into a usable object is itself an async step. Two awaits, two waits — one for the response, one for reading its body.
Heads up

The number-one beginner bug: forgetting await and trying to use the promise as if it were the data. const data = fetch(url) gives you a promise object, and data.results is undefined. If a value is mysteriously undefined or a "Promise" prints where you expected data, a missing await is the first thing to check. Understanding why — that the value isn't there yet — is worth more than the fix.

Read the Open-Meteo geocoding docs as you do this. Learning to find the one parameter you need in a page of documentation is a real skill, and it's the one being trained right now.

Milestone 2 — Get the forecast and show it

Now the second call. Once you have coordinates, ask for the weather:

async function getWeather(lat, lon) {
  const url =
    "https://api.open-meteo.com/v1/forecast?latitude=" +
    lat +
    "&longitude=" +
    lon +
    "&current=temperature_2m,weather_code";
  const response = await fetch(url);
  const data = await response.json();
  return data.current;
}

Again: log it, read the shape, then render it. Put the result on the page instead of the console:

function render(cityName, current) {
  const result = document.getElementById("result");
  result.innerHTML =
    "<h2>" + cityName + "</h2>" +
    "<p>" + current.temperature_2m + "°C</p>";
}

Chain the two calls together so a city name flows all the way to a rendered card:

async function showWeather(city) {
  const geo = await getCoordinates(city);
  const place = geo.results[0];
  const current = await getWeather(place.latitude, place.longitude);
  render(place.name, current);
}

showWeather("Tokyo");

Notice the shape: showWeather awaits the first call, uses its result to make the second, and awaits that. Async code reads top-to-bottom like normal code — that's the whole gift of await. Each line genuinely waits for the one before it, so you can reason about it in order.

Replace the hardcoded "Tokyo" with what the user types. Listen for the form's submit:

document.getElementById("search").addEventListener("submit", (event) => {
  event.preventDefault();
  const city = document.getElementById("city").value.trim();
  if (city) showWeather(city);
});

event.preventDefault() is the line everyone forgets, and forgetting it is baffling until you understand it: a form's default behavior is to reload the page, which wipes your result the instant it appears. You're telling the browser "I'll handle this myself, don't reload." Knowing why the page flashed and cleared — not just that this line fixes it — is exactly the kind of understanding that compounds.

Search a few cities. You have a working weather app. Commit it now — this is the natural checkpoint to run Cartara against your repo (see the last section).

Milestone 4 — The unhappy paths (this is where it gets real)

Right now, type "asdfgh" and the app breaks silently — geo.results is undefined and the code throws. Real apps don't do that. Handle the three states every data-driven app has:

  • Loading. A network call takes time; show something during it. Set the result area to "Loading…" before the await, so the user knows it's working.
  • Not found. If geo.results is missing or empty, the city didn't match. Show a friendly "Couldn't find that city" instead of crashing.
  • Error. The network can fail — offline, API down. Wrap the calls in try/catch and show a real message.
async function showWeather(city) {
  const result = document.getElementById("result");
  result.textContent = "Loading…";
  try {
    const geo = await getCoordinates(city);
    if (!geo.results || geo.results.length === 0) {
      result.textContent = "Couldn't find that city. Try another spelling.";
      return;
    }
    const place = geo.results[0];
    const current = await getWeather(place.latitude, place.longitude);
    render(place.name, current);
  } catch (err) {
    result.textContent = "Something went wrong. Check your connection and try again.";
  }
}
Tip

This milestone is what separates a tutorial-follower from a developer. Anyone can render the happy path. Thinking through "what if the city doesn't exist? what if the network is down?" — and handling each on purpose — is the actual job. If an interviewer asks you to walk through this project, this is the part worth talking about.

Optional polish once the states work: map Open-Meteo's numeric weather_code to a word and an emoji ("Clear ☀️", "Rain 🌧️") by reading the code table in their docs. It's a small, satisfying exercise in — again — reading the documentation.

Milestone 5 — Put it on the internet

A project without a URL is invisible. Because this is just static files, deploying is nearly free:

  1. Push the folder to a GitHub repo.
  2. Turn on GitHub Pages (repo Settings → Pages → deploy from your main branch), or drag the folder onto Netlify. Either gives you a public URL in minutes.
  3. Open the live link on your phone and search your own city.

That's the loop that makes it real: it's not "code on my laptop," it's a thing on the internet that works for anyone.

Stuck? Study and remix

  • Open-Meteo — the API's own repo and docs. When a parameter isn't doing what you expect, the answer is in there.
  • public-apis/public-apis — a giant list of free APIs. The best way to prove you learned this rather than copied it: rebuild the same app against a different API (currency rates, a dictionary, dog photos). If you can, the skill is yours.

Build it with Cartara

You could get this working by pasting each block and never quite grasping why await matters or why the page kept reloading. It would run — and it would fall apart the moment you tried to change it, or an interviewer asked "what does await actually do here?" That gap between "it works" and "I understand it" is invisible from the outside, and it's the exact thing that gets bigger when an AI assistant writes the code for you.

That gap is what Cartara exists to close. As you build:

  • Point Cartara at your repo. It watches the code that lands — including anything an AI helper generated — and turns each real change into a moment to check your understanding, not just a commit.
  • Explain your changes back. When you add the two-call flow or the error handling, Cartara asks you the why — the same question a reviewer or interviewer would.
  • See your comprehension, not just your output. You get an honest signal on which parts of your own first app you truly understand — so you can shore up the weak spots before you move on to something harder.

That's the whole idea behind this library: build something real, then prove to yourself you understand it. The weather app is the project. The understanding is the point.

Make your first real app one you actually understand

Point Cartara at your weather-app repo and it captures what you genuinely learned — the honest way to turn a first project into a skill you own.

Join the waitlist

Reference implementations

Production code worth studying — afteryou've built your own version.

open-meteo/open-meteo
The free, no-key weather API this build uses — its README documents every endpoint and parameter you'll call.
public-apis/public-apis
A huge catalogue of free APIs. Once the weather app works, rebuild it against a different source to prove the skill transferred.
← All projects

Build it with Cartara

Point Cartara at your repo as you build this project. It captures what you actually learned from every change — including the code an AI assistant wrote — so an AI-assisted build becomes understanding you can prove.

Join the waitlist