Building Type-Safe API Clients with OpenAPI and TypeScript
Every frontend developer has been there: you fetch data from an API, access a field that should exist, and get undefined at runtime. TypeScript was supposed to prevent this. The problem is that response.json() returns any, so the compiler has nothing to check against. This guide shows you how to fix that properly—by generating types directly from your OpenAPI specification and using them to build type-safe REST clients in TypeScript.
Key Takeaways
- Manually written TypeScript interfaces drift from your actual API, creating a false sense of safety that leads to runtime errors.
- OpenAPI code generation produces types directly from your API spec, keeping your frontend and backend in lockstep.
- Use
openapi-typescriptwithopenapi-fetchfor a lightweight, fetch-based setup, or Orval for generated TanStack Query hooks and full client SDKs. - Automate type generation in CI or your build scripts so types never go stale.
- Pair generated types with a runtime validator like Zod for critical endpoints where compile-time safety alone isn’t enough.
Why Manual Typing Breaks Down
The naive fix is writing interfaces by hand:
// ❌ Compiles fine, but TypeScript is just trusting you
const data = (await response.json()) as User
This gives you autocomplete, but it becomes a lie the moment the backend changes. Your types drift from reality, and you’re back to runtime surprises.
The better approach: generate types from the source of truth—your OpenAPI spec.
The OpenAPI TypeScript Code Generation Workflow
The typical workflow looks like this:
- Start with an OpenAPI spec (YAML or JSON, hosted or local)
- Run a generator to produce TypeScript types or a full client
- Import those types into your application code
This keeps your frontend in sync with the backend automatically. When the spec changes, you regenerate and TypeScript tells you exactly what broke.
Choosing Your Approach: Types Only vs. Full Client SDK
There are two main strategies for OpenAPI TypeScript code generation, and the right choice depends on your project.
| Approach | Tools | Best For |
|---|---|---|
| Generate types, bring your own fetch | openapi-typescript + openapi-fetch | Lightweight, fetch-based projects |
| Generate a full client SDK | Orval, OpenAPI Generator | Teams wanting ready-made hooks and clients |
Types-only generation keeps your bundle small and lets you control the HTTP layer. Full SDK generation saves wiring time but adds more generated code to maintain.
OpenAPI 3.0 vs 3.1: Most tools support OpenAPI 3.0 well. OpenAPI 3.1 support varies—check your generator’s documentation before assuming full compatibility.
Discover how at OpenReplay.com.
Approach 1: openapi-typescript with openapi-fetch
This is the minimal-runtime path. Generate types from your spec, then use openapi-fetch as a thin, fully-typed wrapper around the native Fetch API.
npx openapi-typescript ./openapi.yaml -o ./src/api/types.ts
npm install openapi-fetch
import createClient from "openapi-fetch"
import type { paths } from "./api/types"
const client = createClient<paths>({ baseUrl: "https://api.example.com" })
// Path, parameters, and response are all type-checked
const { data, error } = await client.GET("/users/{id}", {
params: { path: { id: 123 } },
})
// TypeScript knows exactly what `data` looks like
console.log(data?.email)
Typos in paths, wrong parameter types, and mismatched response shapes all become compile errors. Minimal runtime overhead and only a small runtime dependency (openapi-fetch).
Approach 2: Orval for Full Client Generation
Orval generates typed API functions and—critically—can output TanStack Query hooks directly from your spec. This is useful when you want data-fetching logic handled for you.
npm install orval --save-dev
Configure orval.config.ts to point at your spec and choose an output mode (fetch, axios, or react-query). Orval then generates functions like useGetUsers() with full type safety baked in.
This approach adds more generated code, but for larger APIs it significantly reduces boilerplate.
Keeping Types in Sync
The real value of OpenAPI TypeScript client generation only holds if you regenerate consistently. Add the generation step to your dev workflow:
{
"scripts": {
"generate:api": "openapi-typescript ./openapi.yaml -o ./src/api/types.ts"
}
}
Run it in CI, or watch for spec changes locally. Some teams commit the generated file; others regenerate on every build. Either works—just make it automatic.
What You Still Need to Handle
Generated types usually provide compile-time safety only. Runtime validation requires additional tooling such as Zod or generator plugins. For critical endpoints, pair your generated types with Zod to validate responses at runtime and catch backend bugs before they reach your UI.
Conclusion
OpenAPI TypeScript code generation is one of the highest-leverage improvements you can make to a frontend codebase. Pick openapi-typescript with openapi-fetch for a lightweight setup, or Orval if you want generated query hooks. Either way, you stop writing types by hand, stop guessing at response shapes, and let the compiler do the work it was designed for.
FAQs
Yes. openapi-typescript supports both OpenAPI 3.0 and 3.1 specs, though some schema features may require testing with your specific generator and specification. Always verify the generated output after upgrading your spec version.
Absolutely. Generated types give you compile-time safety, while Zod validates data at runtime. You can define Zod schemas that mirror your generated types and parse API responses through them. This catches cases where the backend returns unexpected data that the compiler cannot detect.
Either approach works. Committing generated files makes builds faster and lets you review type changes in pull requests. Regenerating on every build ensures types are always fresh but adds a build step dependency. Pick whichever fits your team's workflow and CI setup.
Orval is purpose-built for TypeScript and outputs TanStack Query hooks, Axios clients, or fetch functions with minimal configuration. OpenAPI Generator supports many languages but produces more verbose TypeScript output. For frontend-focused teams, Orval typically requires less customization to get clean, idiomatic code.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.