Building a Weather API CLI with TypeScript โ€” async/await, fetch, and Response Types A Java engineer learning TypeScript built a Weather API CLI that retrieves current weather data from the Open-Meteo API using async/await and fetch. The project demonstrates modeling API response shapes with TypeScript types, including the use of `keyof typeof` to derive city name types from an existing coordinates object. The developer implemented unit tests with `jest.spyOn` to mock the fetch function and validate API response handling. This is my sixth article as a Java engineer learning TypeScript from scratch. In my previous article, I built a Quiz CLI and learned about enum , tuple types, and mocking an entire module with jest.mock . This time, I built a Weather API CLI and focused on: async/await fetch type to model the JSON shape response.ok keyof typeof fetch with jest.spyOn global, "fetch" toMatchObject + expect.any Same as always โ€” I write honestly about where I got stuck, what I thought through, and what I asked AI for. ๐Ÿ’ก Learning companionsI use Claude Pro design discussions and Q&A andCursor Pro coding support as learning companions.My rules: I write all the code myselfโ€” I never ask AI to write code for me- AI helps with hints, spec clarification, and bug spotting - I make sure I understand whysomething works before moving on In this article, I clearly separate "what I implemented myself" from "what I asked AI for." A CLI weather tool. Pick a city, or enter a latitude and longitude, and it shows the current weather, temperature, humidity, and wind speed. Weather API Call CLI ================================================ 1. Select a city and run 2. Enter latitude and longitude and run 3. Exit Choose: 1 1. Sapporo 2. Sendai 3. Tokyo 4. Nagoya 5. Osaka 6. Fukuoka 7. Kagoshima 8. Naha Choose a city: 3 Tokyo weather: Partly cloudy Tokyo temperature: 20.4ยฐC Tokyo humidity: 80% Tokyo wind speed: 3.1km/h ================================================ Weather data comes from the free Open-Meteo https://open-meteo.com/ API no API key required . ๐Ÿ“ฆ Repository: https://github.com/uya0526-design/weather api https://github.com/uya0526-design/weather api weather api/ โ”œโ”€โ”€ src/ โ”‚ โ”œโ”€โ”€ index.ts Entry point / CLI menu โ”‚ โ”œโ”€โ”€ api.ts API call logic โ”‚ โ”œโ”€โ”€ types.ts Type definitions โ”‚ โ””โ”€โ”€ tests / โ”‚ โ””โ”€โ”€ api.test.ts Unit tests โ”œโ”€โ”€ jest.config.js โ”œโ”€โ”€ tsconfig.json โ””โ”€โ”€ package.json Same three-layer structure as the previous projects: types.ts types / api.ts logic / index.ts UI . New this time: communicating with an external API is the main event. readline and fetch The heart of the type design this time was how to express an API response as a type . I defined the list of selectable cities and their latitude, longitude, and timezone. js // List of selectable cities export const cityList = "Sapporo", "Sendai", "Tokyo", "Nagoya", "Osaka", "Fukuoka", "Kagoshima", "Naha", ; // Latitude / longitude / timezone per city export const cityCoordinates = { "Tokyo": { latitude: 35.68123, longitude: 139.76712, timezone: "Asia/Tokyo", }, // ... }; The quietly useful tool here is keyof typeof . It lets me pull out just the keys of cityCoordinates the city names as a type. type CityName = keyof typeof cityCoordinates; // becomes a union type: "Sapporo" | "Sendai" | "Tokyo" | ... In Java I'd define the cities as an enum . In TypeScript I can reuse the keys of an object that already exists directly as a type โ€” that felt fresh. This was the main challenge. I called the API first, checked the actual response JSON, and modeled that shape directly with type . export type WeatherResponse = { latitude: number; longitude: number; timezone: string; timezone abbreviation: string; current units: { temperature 2m: string; relative humidity 2m: string; weather code: string; wind speed 10m: string; }; current: { time: string; interval: number; temperature 2m: number; relative humidity 2m: number; weather code: number; wind speed 10m: number; }; }; โš ๏ธ A type that didn't match realityAt first I defined temperature 2m and the other fields inside current as string . Looking at the actual API response, they're numbers, so number is correct โ€” and AI caught this for me more below . A wrong type definition leads to runtime errors, so I felt firsthand how important it is to check the type against the real response. Open-Meteo returns WMO World Meteorological Organization weather codes as numbers. I defined a mapping to convert them to readable text. js export const WeatherCode = { 0: { name: "Clear" }, 1: { name: "Mainly clear" }, 2: { name: "Partly cloudy" }, 3: { name: "Cloudy" }, 45: { name: "Fog" }, // ... 95: { name: "Thunderstorm" }, }; ๐Ÿ“ I first tried writing this as an enumI originally went for enum , but I decided on my own that a "code โ†’ text" mapping reads more naturally as a const object than as an enum . An enum is "a named set of constants," while an object is "a key-to-value mapping" โ€” the difference in purpose started to feel intuitive. At this point I had written the keys as the string "0" , which didn't line up with the numeric weather code from the API, so I fixed them to the numeric key 0 also an AI catch . The getWeather function takes a latitude, longitude, and timezone, and calls the API. export async function getWeather latitude: number, longitude: number, timezone: string | undefined, : Promise