TypeScript Tutorial: Validate API Data at the Boundary
Web Development Learning Series — Part 5 of 10 · Intermediate
TypeScript can catch many mistakes before a program runs, but an API response is still outside your control. A server can send an unexpected value even if your editor says the variable is a Course. This lesson shows where static types help and where a runtime check is necessary.
You need basic JavaScript objects, arrays, and functions. Run the examples in the TypeScript Playground or a project configured with the current TypeScript compiler and strict checking.
Describe a small domain
type Level = 'beginner' | 'intermediate' | 'advanced';
type Course = {
id: string;
title: string;
level: Level;
};
function label(course: Course): string {
return course.title + ' — ' + course.level;
}This model makes the accepted difficulty values explicit. It also gives a function a clear input and output. Try passing an object without a title and read the compiler message. Use the error to understand the contract rather than suppressing it immediately.
Start untrusted data as unknown
A type assertion is a statement to the compiler, not a check against the network. If you assert that arbitrary JSON is a Course, the server has not agreed to that claim. Accept unknown at the boundary and narrow it with actual checks.
function isCourse(value: unknown): value is Course {
if (typeof value !== 'object' || value === null) return false;
const item = value as Record<string, unknown>;
return typeof item.id === 'string'
&& typeof item.title === 'string'
&& (item.level === 'beginner'
|| item.level === 'intermediate'
|| item.level === 'advanced');
}
function parseCourses(value: unknown): Course[] {
if (!Array.isArray(value) || !value.every(isCourse)) {
throw new Error('Invalid course response');
}
return value;
}
const incoming: unknown = [
{ id: 'html', title: 'HTML Foundations', level: 'beginner' }
];
const courses = parseCourses(incoming);
console.log(courses.map(label));Keep validation proportional
The example checks shape and accepted level values. It does not enforce nonempty titles, unique identifiers, or a maximum array length. Those are additional business rules. Add them when the application needs them instead of assuming the type guard covers every possible requirement.
For a larger API, a schema library can help centralize checks and error reporting. Evaluate it against your project's needs and dependency policy. The basic principle remains the same: validate external input once, then use a trusted internal representation.
Model states that cannot contradict each other
type LoadState =
| { status: 'loading' }
| { status: 'success'; courses: Course[] }
| { status: 'error'; message: string };
function describe(state: LoadState): string {
switch (state.status) {
case 'loading': return 'Loading…';
case 'success': return state.courses.length + ' courses';
case 'error': return state.message;
}
}This is easier to reason about than separate loading, error, and successful booleans that can all become true. Each state contains only the data relevant to that outcome. When your domain gains another state, update both the type and the rendering behavior.
Practice task
Feed parseCourses a null value, a missing title, an unknown level, and an empty array. Decide which should be accepted. Then add a rule that identifiers must be unique, and explain whether failure should reject the whole response or only individual records.
Do not use any simply to make errors disappear. Prefer a small, honest type that reflects your understanding. TypeScript becomes useful when it makes the program's assumptions visible to its next maintainer.
Continue your learning
Previous lesson: JavaScript Fetch API: Loading, Errors, and Cancellation Explained. Explore our web development series and the HTML multimedia project.
Reference: Official documentation for this lesson. Examples are learning exercises; adapt and test them for your own application.
Comments
Post a Comment