The brief: a typed weather API client
You have learnt the whole type system — primitives to conditional types, narrowing to generics, async to validation. This capstone is where it stops being a list of features and becomes a way of building. Over five lessons you will build one real thing, end to end, applying the course's judgement at every step: a typed weather client that fetches live weather for any location and turns an untrusted JSON response into a value the rest of your program can trust completely.
What we are building
A small library — no framework, no UI, just typed TypeScript — with one job: given a latitude and longitude, return the current weather as a clean, fully-typed value, and handle every way that can go wrong without ever crashing. Concretely, this function:
getWeather(19.07, 72.87, fetch) // Mumbai
// → { ok: true, value: { temperatureC: 27.1, windSpeedKmh: 0.4, condition: "cloudy" } }
or, when something fails:
// → { ok: false, error: { kind: "http", status: 503 } }
It is deliberately small enough to build properly in five lessons, and deliberately real: it calls a live public API, deals with genuine untrusted data, and models the failures that actually happen. When you finish, you will have written the kind of typed boundary that sits at the edge of every professional codebase.
The API: Open-Meteo (free, no key)
We use Open-Meteo — a free weather API that needs no sign-up and no API key, so you can run every line of this capstone yourself. The endpoint we call:
https://api.open-meteo.com/v1/forecast?latitude=19.07&longitude=72.87¤t=temperature_2m,wind_speed_10m,weather_code
A real response looks like this (trimmed to what we use):
{
"current": {
"time": "2026-09-27T21:15",
"temperature_2m": 27.1,
"wind_speed_10m": 0.4,
"weather_code": 1
}
}
Three fields matter to us: temperature_2m (°C), wind_speed_10m (km/h), and weather_code — a WMO
numeric code where 0 is clear, 61 is rain, 95 is thunderstorm, and so on. Part of our job is turning
that opaque number into a meaningful condition. Try the URL in your browser now; seeing the real shape is
the first step.
The requirements — the standard the capstone is built to
This is not "make it work". It is "build it the way the best-practices and real-projects modules said to". The requirements encode that:
- The public value is fully typed and trustworthy. Callers get a
Weathervalue with a numeric temperature, a numeric wind speed, and aconditiondrawn from a small closed set — never a rawweather_code, neverany, never a possibly-missing field. - The boundary is validated, not asserted. The JSON from Open-Meteo is untrusted (the typing-apis
lesson): it arrives as
unknownand is checked before we believe it — noas ApiResponse. - Every failure is modelled, never thrown into the void. Network failure, an error HTTP status, and a malformed body are three distinct, typed outcomes the caller can handle — using the Result pattern, so a failure is a value, not an exception that a caller might forget to catch.
- Illegal states are unrepresentable. You cannot construct a
Weatherwith aconditionoutside the known set, and you cannot readvalueon a failed result — the types forbid both. - It is testable without the network.
fetchis injected, so the whole client can be exercised against fake responses — success, each failure — with no live call. This is dependency injection from the design-patterns module, earning its place.
Every one of these is a decision the course argued for. The capstone is where you see them combine into something that could not have been built as safely in plain JavaScript.
How the five lessons go
We build in the order a careful engineer actually works — types first, then implementation, then hardening, then tests:
- Design (next lesson): model the domain in types before writing any logic — the
Weathervalue, theConditionset, theResulttype, the typedWeatherError. Make the illegal states impossible on paper first. - Build: implement the client — the request, validating the untrusted response into our domain model,
mapping the WMO code to a
condition. Get the happy path working against the real API. - Hardening: handle every failure exhaustively, render each outcome, and use the compiler's exhaustiveness check so a new error kind cannot be silently ignored.
- Testing: exercise the whole thing against injected fake responses — every success and failure path — plus type-level tests that assert the types themselves are what we intend.
Design first is not ceremony. In a typed language, the types are the design — get them right and the implementation often writes itself, because the compiler rejects everything that does not fit. That is the lesson the whole capstone is meant to land.
Before you start
Set up a scratch project you will build across all five lessons:
mkdir weather-client && cd weather-client
npm init -y
npm install --save-dev typescript tsx
npx tsc --init --strict
Set "target": "ES2022" and "lib": ["ES2022", "DOM"] in the generated tsconfig.json (we use fetch
and promises — the tsconfig lesson explains why DOM and a modern target are needed). You will run files
with npx tsx <file>.ts and type-check with npx tsc --noEmit, exactly as throughout the course. Confirm
the setup works with a one-line console.log("ready") before moving on.
Check your work
What we are building. A typed weather client: latitude/longitude in, a fully-typed Weather value out,
every failure handled — a real typed boundary, no framework.
The API. Open-Meteo — free, no key — returning current.temperature_2m, wind_speed_10m, and a WMO
weather_code we map to a condition.
The five requirements. Fully-typed trustworthy value; validated (not asserted) boundary; every failure
modelled as a typed Result; illegal states unrepresentable; testable via injected fetch.
Why design-first. In a typed language the types are the design — getting them right makes the implementation fall out and the compiler reject what does not fit.
The build order. Design → build → hardening → testing.
Practice
- Open the Open-Meteo URL in your browser for your own city's coordinates and read the real
currentobject. Note which fields we use and which we ignore. - Look up three WMO weather codes (try 0, 61, 95) and decide what
conditioneach should map to. - Set up the
weather-clientproject above and confirmnpx tsxruns a one-liner. - Before reading the next lesson, sketch on paper the types you think this needs: what does
Weatherlook like? What are the possible failures? You will compare your sketch to the designed version next. - Write down, for each of the five requirements, which course module it came from — a quick map of what the capstone is exercising.
Official documentation
- Open-Meteo — Forecast API — The live API this capstone calls; free, no key.
- WMO weather interpretation codes — The
weather_codevalues we map to conditions. - TypeScript — Everyday Types — A refresher on the building blocks the design uses.
Next: designing the domain in types, before a line of logic.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship