When working with API responses on the frontend, we often start with an implicit assumption:
If types are generated from OpenAPI, the response is safe.
The combination of TypeScript and OpenAPI significantly improves frontend productivity.
However, it does not guarantee the safety of API responses at runtime.
TypeScript types exist only at compile time.
Data received from the server at runtime still lives in a zone of trust.
In this post, I revisit a Swagger-based API generation setup and redefine how much responsibility the frontend should reasonably take for API responses, and why I ended up choosing Orval in that process.
This project adopted swagger-typescript-api from the beginning, generating type definitions and API functions directly from OpenAPI documents.
This approach had clear advantages:
However, the limitations were equally clear:
I never experienced a production incident caused by runtime type mismatches.
But that was not because the structure was inherently safe — it was simply because the problem had not occurred yet.
Zod was already part of the codebase, but its responsibility was clearly scoped to user input (User I/O) validation.
Extending Zod to API responses presented two options:
The first option had a high cost with unclear returns. The second meant the frontend would take no responsibility at all for API response correctness.
What I needed instead was a way to derive both static types and runtime validation from the same source.
Orval can generate the following directly from OpenAPI specifications:
This made it possible to keep the API specification as a single source of truth while securing both static typing and runtime validation.
Frontend code no longer needed to manually manage Zod schemas.
API response validation could be handled consistently through a shared parsing layer.
The migration focused on preserving the existing structure as much as possible:
select stageThis approach minimized structural changes while introducing a validation gate before data reached the UI.
When invalid responses appear, the UI no longer renders broken states. Instead, they follow a clear validation error flow.
Although the number of generated files increased, the Vite-based bundling setup ensured that only actually used code was included.
Since gzip compression was already in place, no additional performance issues were introduced.
This structure does introduce a clear learning curve:
After the migration, team members unfamiliar with the structure faced a noticeable learning cost.
This was a deliberate trade-off:
consistency and safety in exchange for higher onboarding effort.
The goal of this migration was not to make API responses “perfectly safe.” Instead, I wanted to redefine API responses on the frontend as follows:
Within these constraints, Orval proved to be a reasonable choice.