# Type Safe APIs with TypeScript
API calls are one of the most common sources of runtime bugs — a backend field gets renamed, and the frontend silently breaks. TypeScript can catch these mismatches before deployment if the API layer is typed properly.
## Typing the Response Shape
```typescript
interface Course {
id: string;
title: string;
durationMonths: number;
}
async function getCourse(id: string): Promise {
const res = await fetch(`/api/courses/${id}`);
return res.json();
}
```
This tells TypeScript what shape to expect, so any code using the result gets autocomplete and type checking — though it's worth noting `.json()` doesn't validate the data at runtime, only at the type level.
## Runtime Validation with Zod
Since TypeScript types disappear at runtime, pairing them with a schema validator like Zod catches actual malformed API responses, not just type mismatches during development:
```typescript
import { z } from "zod";
const CourseSchema = z.object({
id: z.string(),
title: z.string(),
durationMonths: z.number(),
});
const course = CourseSchema.parse(await res.json());
```
## Typed API Clients
Generating types directly from your backend (via OpenAPI or Prisma-generated types) keeps frontend and backend in sync automatically, instead of manually maintaining duplicate interfaces.
## Error Handling Types
```typescript
type ApiResult = { success: true; data: T } | { success: false; error: string };
```
This pattern forces calling code to handle both success and failure cases explicitly, rather than relying on try/catch alone.
## Conclusion
Type-safe APIs aren't just about writing interfaces — they require runtime validation too, since types alone can't protect against a backend actually sending different data than expected.
Back to Blogs
Type Safe APIs with TypeScript
How to design API layers in TypeScript so a backend response shape mismatch gets caught before it reaches production.
06 Aug 2026
6 min read