Build Bulletproof TypeScript API Clients from OpenAPI with Zod — A Practical Guide
Surprising fact: even with TypeScript, production bugs caused by invalid API responses remain common — teams that add runtime validation report up to a 3x faster mean‑time‑to‑detect for contract mismatches. If you generate TypeScript client OpenAPI types but skip runtime checks, you’re leaving a large class of failures invisible until production.

Why runtime validation still matters in TypeScript
TypeScript's static types are great for developer ergonomics, but they are erased at runtime. Common pitfalls with generated types:
- Server drift: APIs evolve and clients can receive fields that don't match the generated type.
- Nullable vs missing: OpenAPI's
nullable,readOnlyandwriteOnlysemantics often differ from TypeScript expectations. - Polymorphism (oneOf/anyOf/discriminator) is typed but not checked, leading to runtime narrowing errors.
Slash TypeScript Build Times in Monorepos: Practical Guide to Incremental Compilation & Caching
- Zod adoption: Zod exceeds ~1M weekly npm downloads (mid‑2024) showing broad runtime validation adoption.
- Tooling usage:
openapi-typescripthas hundreds of thousands of weekly installs for generating static types from OpenAPI specs. - Developer impact: teams adding runtime validation report significantly fewer contract mismatches in staging (typical reductions of 30–60% in client/server schema issues).
Tooling overview
The typical workflow to generate type-safe TypeScript API clients from OpenAPI using Zod uses these tools:
- openapi-typescript — fast conversion from OpenAPI -> TypeScript types.
- OpenAPI Generator — more flexible templates (client code, fetch/axios wrappers).
- Zod — runtime validation schemas that integrate tightly with TypeScript.
- zod-to-ts and related codegen helpers — convert TS types to Zod schemas or emit Zod schemas directly.
Recommendation: generate static types first, then emit Zod schemas programmatically (or via templates) so you get both compile-time types and runtime validators.
Step-by-step: generate types and convert to Zod
Quickstart (copy these into your repo):
npm install -D openapi-typescript zod zod-to-ts
Generate TypeScript types from your openapi.yaml:
npx openapi-typescript openapi.yaml --output src/api/types.ts
Then convert a type into a Zod schema using zod-to-ts (this example shows the pattern — in the full product we include generator templates):
import { z } from 'zod';
import { schema } from 'zod-to-ts';
// manual example
export const UserSchema = z.object({
id: z.string(),
email: z.string().email(),
displayName: z.string().optional(),
});
export type User = z.infer;
Polymorphism & discriminators
For OpenAPI oneOf with a discriminator, emit a z.union and add a z.preprocess or refine to ensure correct branch selection. That pattern is essential for reliable type narrowing.
Create a small client wrapper that validates responses
Minimal fetch wrapper that validates responses with Zod:
import { z } from 'zod';
async function request(url: string, schema: z.ZodSchema, opts?: RequestInit): Promise {
const res = await fetch(url, opts);
const json = await res.json();
const parsed = schema.safeParse(json);
if (!parsed.success) {
// Map Zod error to your error type
throw new Error('Response validation failed: ' + JSON.stringify(parsed.error.format()));
}
return parsed.data;
}
// usage
const user = await request('/api/user/123', UserSchema);
Axios is similar — validate response.data before returning. This simple pattern prevents corrupted assumptions from propagating through your app.
CI and developer ergonomics
Automation is crucial. Recommended steps:
- Regenerate types/schemas on spec changes via a GitHub Action that runs
openapi-typescript+ your Zod template generator. - Run type-aware tests that call endpoints on a mock server (WireMock / Prism) and assert schema conformance.
- Protect hand-written code by placing generated code in
src/genand never editing it directly — use adapters for customization.

Performance and bundle-size considerations
Zod adds runtime cost. Practical optimizations:
- Tree-shake: export only schemas you need from the generated bundle.
- Lazy validation: validate externally for large payloads, or validate only critical fields client-side.
- Selective generation: emit input (write) schemas and output (read) schemas separately to avoid duplication.
Real-world pitfalls and best practices
Common issues teams face:
- Versioning — always tag generated clients to API version and include the spec hash in the generated header.
- Partial responses — support partial schemas with
z.partial()when endpoints return sparse objects. - Nullable/optional — prefer explicit
z.union([z.null(), ...])for nullable fields to avoid mismatches.
Case study: an anonymized fintech company used this pattern to convert a hand‑rolled fetch layer to a generator + Zod validators approach. After adding response validation and a small integration test suite, they saw API contract errors caught in CI instead of production, reducing incident triage time by ~40%.
API design best practices and debugging techniques are useful follow-ups if you're reworking your API surface.
Unique insight: defense-in-depth across lifecycle
Here's a differentiator: don’t treat Zod as only client‑side safety. Use the same generated Zod schemas for:
- Client runtime validation
- Server input validation (middleware)
- Contract tests that assert the server response matches the client expectation
Using the same validators across the stack enforces a true contract, provides clearer error messages, and surfaces API drift earlier.
Conclusion
Generating TypeScript types from OpenAPI is a solid first step, but combining that with Zod runtime validation gives you a much stronger guarantee that your type-safe API client TypeScript actually matches reality. Whether you use openapi-typescript + zod-to-ts, custom OpenAPI Generator templates, or a hybrid approach, the pattern is the same: generate types, emit Zod schemas, validate at the boundaries, and automate regeneration in CI.
Get the complete guide with generator templates, GitHub Actions, discriminators patterns, and bundle‑size checklists in our digital product. Get the complete guide with templates and checklists in our digital product.
microservices architecture
Call to action: Get the complete guide with templates and checklists in our digital product — ready‑to‑drop into your repo and customized for teams.
Comments
Post a Comment