Hands‑On Exercise: Build a Safe Parser for External API Data¶
We’ll simulate an API that returns a list of products.
The API is unreliable—it may return malformed data, missing fields, or wrong types.
Your job is to:
- Define the expected TypeScript types
- Write custom type guards
- Validate the external data
- Narrow types safely
- Use exhaustiveness checking to ensure all cases are handled
Let’s go step by step.
Step 1 — Define the Expected Data Shape¶
Create a file:
types.ts¶
export type Product = {
id: number
title: string
price: number
tags: string[]
}
This is the ideal shape we want to enforce.
Step 2 — Simulate External API Data¶
Create:
api.ts¶
export async function fetchProducts(): Promise<unknown> {
// Simulated external API response
return JSON.parse(`
[
{ "id": 1, "title": "Laptop", "price": 1299, "tags": ["tech"] },
{ "id": "oops", "title": "Phone", "price": 799, "tags": ["tech"] }
]
`)
}
Notice the second product has an invalid id (string instead of number).
TypeScript cannot catch this at compile time—this is runtime data.
We must validate it.
Step 3 — Write Type Guards¶
Create:
guards.ts¶
Start with a guard for a single product.
import { Product } from "./types"
export function isProduct(value: unknown): value is Product {
return (
typeof value === "object" &&
value !== null &&
typeof (value as any).id === "number" &&
typeof (value as any).title === "string" &&
typeof (value as any).price === "number" &&
Array.isArray((value as any).tags) &&
(value as any).tags.every((t: unknown) => typeof t === "string")
)
}
Now a guard for an array of products:
export function isProductArray(value: unknown): value is Product[] {
return Array.isArray(value) && value.every(isProduct)
}
This is a composable guard—a best practice in TypeScript.
Step 4 — Parse and Validate the Data¶
Create:
parser.ts¶
import { fetchProducts } from "./api"
import { isProductArray } from "./guards"
import { Product } from "./types"
export async function loadProducts(): Promise<Product[]> {
const data: unknown = await fetchProducts()
if (!isProductArray(data)) {
throw new Error("Invalid product data received from API")
}
return data
}
TypeScript now guarantees:
loadProducts()returnsProduct[]- Every product has correct types
- No unsafe property access
Step 5 — Use the Parser in Your App¶
Create:
index.ts¶
import { loadProducts } from "./parser"
async function main() {
try {
const products = await loadProducts()
for (const p of products) {
console.log(`${p.title} — $${p.price}`)
}
} catch (err) {
console.error("Failed to load products:", err)
}
}
main()
Run it:
ts-node index.ts
You should see an error because the second product is invalid.
This is exactly what we want.
Step 6 — Add Exhaustiveness Checking (Bonus)¶
Let’s extend the API to return:
- success
- error
- loading
api.ts¶
export type ApiResponse =
| { status: "success"; data: unknown }
| { status: "error"; message: string }
| { status: "loading" }
Handle all cases safely¶
import { ApiResponse } from "./api"
import { isProductArray } from "./guards"
function handleResponse(res: ApiResponse) {
switch (res.status) {
case "success":
if (!isProductArray(res.data)) {
throw new Error("Invalid product data")
}
return res.data
case "error":
throw new Error(res.message)
case "loading":
console.log("Loading...")
return []
default:
const _exhaustive: never = res
return _exhaustive
}
}
If someone adds a new variant:
{ status: "empty" }
TypeScript will immediately flag missing cases.
This is industrial‑strength safety.
What You Learned¶
This hands‑on exercise demonstrates how to safely handle external data in TypeScript using:
1. Custom type guards¶
value is Typefunctions- Composable guards
- Array guards
2. Narrowing¶
- Using guards to refine
unknown - Safe property access
- Eliminating runtime errors
3. Exhaustiveness checking¶
neverin switch statements- Catching missing union cases
- Ensuring future‑proof logic
4. Real‑world application¶
- Parsing API responses
- Validating JSON
- Handling unreliable external data
This is exactly how professional TypeScript teams build robust, production‑ready systems.