Introduction of API Code Automation Based on Orval
1. Introduction Background
In frontend development, integrating APIs and writing types is a frequent and repetitive task. Whenever a new API is added or when request parameters and response structures change, type definitions need to be modified, API call functions written, and TanStack Query hooks configured. When the number of APIs is small, writing them manually isn't a big issue, but as the project scale increases, the number of files that need to be modified grows, making it easy for human errors such as field name typos or type mismatches to occur.
Especially in environments where the backend and frontend are developed in parallel, the Swagger documentation can change continuously. In this case, manually managing types and call functions in the frontend can easily lead to discrepancies between the actual API specifications and the code. For example, if a response field name has changed or the optionality has been altered, modifying only some files can result in runtime errors or TypeScript errors.
To reduce such issues, in the projects I participated in, we introduced Orval, which automatically generates API code based on the Swagger documentation. Orval is a tool that can generate TypeScript types and API call functions based on OpenAPI or Swagger specs and can also generate TanStack Query-based hooks depending on the configuration.
The purpose of the introduction was not just to generate code quickly, but to reduce the discrepancies between the API specifications and the frontend code. The main goal was to automate repetitive boilerplate code so that developers could focus more on screen logic, state management, exception handling, and improving user experience.
In a monorepo environment, it is common for multiple apps or packages to share common API types and call logic. By using Orval, we were able to generate a common API layer based on Swagger standards and utilize it in each package, which reduced duplication and maintenance burden.
2. Issues encountered during actual implementation
A typical problem experienced while implementing Orval was a case where type errors occurred due to failing to properly detect the version of TanStack Query in a monorepo environment.
The project was managed using a combination of monorepo and pnpm. In this structure, instead of installing all dependencies at the root, dependencies are often declared only for the packages that are actually used. At that time, TanStack Query was only installed in the packages that used the actual API hooks and did not exist at the monorepo root.
The problem was that the Orval execution command was being run from the monorepo root. Orval determines the project environment based on the execution location, and at the root, it was unable to correctly detect the TanStack Query v5 version installed in the packages using the actual API hooks. As a result, the code generated at that time was based on v4 instead of v5, causing TypeScript errors when conflicting with the options object type of TanStack Query v5 used in the actual project.
At first, it seemed simply like a type issue with the generated code, but upon investigation, the key was how Orval determined the version, rather than the generated code itself. From a developer's perspective, it was thought that TanStack Query v5 was being used in the project, but the tool failed to find that dependency at the root location where it was executed. In other words, the actual usage environment and the execution environment judged by the tool were different.
One more point to note about this issue is that if an attempt was made to resolve it by directly modifying the generated files, there was a high likelihood that the same problem would recur during the next run of Orval. Automatically generated files are overwritten when regenerated, so directly modifying the generated results is unlikely to be a fundamental solution.
3. Cause Analysis and Solutions
The cause of this issue is that the execution location of Orval and the actual dependency location were different, and that there was a lack of precise control over code generation based on TanStack Query v5. While automation tools are convenient, we cannot say they function without a complete understanding of the project structure. Especially in complex scenarios like monorepos, pnpm workspaces, and package-specific dependency management, the intended results may not be achieved with just the default settings.
There were three solutions to consider when a problem occurred.
Firstly, it is a method to change the execution location of Orval based on the package where TanStack Query is installed. By adjusting the execution script to run from the respective package directory, Orval can detect the correct dependencies.
Secondly, it's a method to add TanStack Query dependency to the root so that Orval can detect the version. However, since this method involves adding a dependency in a location that is not actually used in the monorepo, it should be considered that it may conflict with dependency management principles.
Thirdly, it's a method to specify the criteria for generating TanStack Query hooks in the Orval configuration file.
I actually solved this by specifying that it should be generated according to TanStack Query v5 in the Orval settings. This method had the advantage of being able to clearly control the code generation criteria without significantly changing the dependency structure. Additionally, since the intention is left in the configuration file, it became easier for team members to identify the cause when encountering the same problem later on.
Additionally, it may be worth considering separating the automatically generated code from the code used in the actual interface. Instead of directly using the API functions and hooks generated by Orval in the screen, applying a method that wraps them once in a custom hook or service layer when necessary can improve maintainability. Keeping the generated code in an area close to the API specs and managing data processing, basic options settings, and error handling separately can minimize the direct impact on screen code even if Orval is regenerated, and it allows for a separation of responsibilities between responding to API changes and screen development.
4. Introduction Effect
The biggest effect of introducing Orval was the reduction of repetitive tasks. By reducing the repetitive task of manually defining types and writing API call functions every time a new API was added, we were able to ease the burden of writing basic API integration code significantly by setting Orval based on React Query, which can also generate TanStack Query hooks.
The second effect was the reduction of human error. When manually typing, it's possible to incorrectly enter field names, omit parts of the response structure, or misjudge the optional nature of fields. By using Orval, types are generated based on the Swagger specification, which helps reduce discrepancies between the API specification and frontend types.
The third effect is that it becomes easier to track changes to the API. By committing the automatically generated files, we can check Git diff to see what types or functions have changed due to Swagger updates. This allows for a review of changes such as API paths, request bodies, and response types based on the code.
Another advantage is that the code and screen logic can be separated based on the API specification. The generated code can be kept in the API layer, and the actual screen can be organized in a way that uses separate custom hooks or services, allowing for a division of responsibilities between API change responses and screen development.
5. Points to note during operation
To use Orval reliably, several criteria are needed.
First, you need to determine the timing of the automatic generation. Running it automatically at build time allows you to always reflect the latest specs, but unintended API changes may suddenly cause the build to break. On the other hand, the method where developers manually execute and review the generated files before committing has the advantage of being able to verify changes, but if execution is missed, the latest specs may differ from the code. You should choose according to the current project's situation and team management style.
You also need to decide which Swagger environment to base the generation on. There needs to be a consensus within the team since the Swagger specs in the development, QA, and production environments may differ. The development environment may quickly reflect the latest changes, but it could include APIs that are not yet finalized, while the production environment is stable but may not incorporate the latest development content. Otherwise, unintended specification changes may be reflected, potentially increasing debugging time.
The scope of generation also needs to be controlled. If all APIs included in the Swagger documentation are generated at once, it may include APIs that are not actually used in the frontend, resulting in unnecessarily large code. Therefore, it is better to generate only the necessary APIs based on tags, paths, and domains.
Finally, it is recommended not to directly modify auto-generated files, as any changes may be overwritten during the next generation. If there is custom logic required, it is safer to handle it in a separate wrapper or custom hook rather than modifying the generated file.
6. Conclusion
Through this experience with Orval, I learned that automation tools are useful for reducing repetitive tasks, but developers must clearly understand and control the project structure and execution environment for stable usage.
In particular, in monorepo and pnpm environments, the behavior of the tools can vary depending on the execution location, dependency declaration location, and package structure. This issue appeared to be a simple type error, but in reality, it was due to Orval not correctly detecting the version of TanStack Query, resulting in code being generated based on the v4 standard at the time, which conflicted with the v5 types used in the actual project.
As a result, Orval was a useful tool that automates API integration tasks and allows for consistent management of types, API call codes, and even TanStack Query hooks based on Swagger and configuration. However, to use it reliably, version specification, environment separation, generation scope control, and file management principles are also necessary.
When adopting automation tools, I believe it's essential to not just consider the advantages of the tool itself, but also to evaluate how it operates within the actual project structure and the criteria by which the outputs are produced.
green