Validate API Responses at the Boundary in TypeScript
Treat incoming JSON as unknown, then check its shape before handing it to the rest of your application.
A woodworking jig can check whether a block fits a particular opening. A block that fits passes through. An ill-fitting piece stays on the other side, where you can inspect it before it reaches the next step.
An API response needs a similar checkpoint. A TypeScript type describes what your code expects, but the server can still send a missing field, a `null`, or a number where you expected a string. A runtime check lets you catch those mismatches where the data enters your code.
Start with `unknown`
Assign the result of `response.json()` to a variable typed as `unknown`. That makes TypeScript require a check before you read fields from it. A type assertion such as `as Product` would let you access those fields immediately without checking what the server sent.
type Product = {
id: string;
name: string;
};
function isProduct(value: unknown): value is Product {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
typeof value.id === "string" &&
"name" in value &&
typeof value.name === "string"
);
}
async function fetchProduct(url: string): Promise<Product> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Product request failed (${response.status})`);
}
const data: unknown = await response.json();
if (!isProduct(data)) {
throw new Error("Expected a product with a string id and name");
}
return data;
}
The `value is Product` return type is a *type predicate*. When the function returns `true`, TypeScript narrows `data` to `Product`, so callers can use `product.name` as a string. A payload such as `{ "id": 42, "name": "Clamp" }` parses as JSON but fails the guard because its `id` is a number. A malformed JSON body fails earlier, during `response.json()`.
Check what the next step needs
This guard accepts extra fields and empty strings. If the application requires a nonempty ID, add that check to the guard after confirming `id` is a string. The guard is like the jig opening. Its dimensions should match what the next step can work with.