API 연동 자동화 2탄 - Orval로 API 연동 자동화하기
API 연동 자동화를 통해 프론트엔드 개발 프로세스를 개선한 경험을 공유하고자 합니다. 기존 API 연동 자동화(feat. OAS codegen) 방식에서 한 단계 더 나아가, 실제 개발에서 필요한 보일러플레이트까지 자동화하는 방법을 공유하고자 합니다.
문제 인식
이전에 API 연동 자동화(feat. OAS codegen)를 통해 기본적인 API 연동 프로세스 자동화는 해결했습니다. 백엔드 개발자의 의도대로 프론트엔드에서 API 연동을 할 수 있게 되어 커뮤니케이션 비용과 휴먼 에러를 크게 줄일 수 있었죠.
하지만 실제 프론트엔드 개발에서는 여전히 반복되는 작업이 필요했습니다:
API 모킹을 통한 테스트
@tanstack/react-query 같은 서버 상태 관리
zod를 활용한 런타임 스키마 검증
결국 기존 OAS codegen만으로는 프론트엔드 개발자가 여전히 많은 보일러플레이트 코드를 작성해야 했습니다.
문제 해결 방식 고민
이 문제를 해결하기 위해 두 가지 방법을 고려하였습니다.
1. 직접 개발하자!
직접 반복되는 생성 코드 script를 개발하는 방법(https://tech.inflab.com/20230613-code-generator-part-1/)
장점
요구사항 100% 커스텀 가능
기존 구현 방법과 호환
자체 구현이므로 다른 의존성이 없음
단점
초기 개발 비용
유지보수 리소스
엣지 케이스 검증 필요
2. 라이브러리를 찾아서 활용하자!
Orval이라는 라이브러리를 발견했습니다.
장점
프로덕션레벨에서 이미 사용되고 있다.
OAS를 쓰는 기존방식에서 벗어나지 않는다.
요구조건(React Query, MSW, Zod)도 만족시켜준다.
단점
새로운 도구의 학습 비용
의존성 추가
커스터마이징 제약
기존 방식 vs 개선된 방식
기존의 방식과 기본적으로는 달라지지 않았습니다. 기본적으로 OAS 스펙의 json을 input으로 output을 만들어낼 수 있었습니다.
주요 개선점
라이브러리 연동
@tanstack/react-query
msw(@faker-js/faker 포함)
zod
이밖에도 다른 라이브러리 연동이 가능합니다(ex. SWR)
custom http client
기존: 지원하는 일부 client (
typescript-axios
)만 지원개선: custom http client를 지원
도입 방법
FE 설정(orval 설정)
import { defineConfig } from "orval";
export default defineConfig({
api: {
input: {
target: "openapi.json", // OAS json
},
output: {
mode: "tags-split", // 태그별로 분리
clean: ["./shared/api/endpoints", "./shared/api/model"], // 재생성시 삭제 폴더
target: "./shared/api/endpoints", // 생성될 파일 경로
schemas: "./shared/api/model", // 생성될 모델 경로
client: "react-query", // TanStack Query
override: {
query: { // 생성할 쿼리 종류(활성화)
useQuery: true,
useInfinite: true,
useSuspenseQuery: true,
},
mutator: {
path: "./shared/api/http.ts", // custom http client
name: "httpClient",
},
},
mock: true, // msw
},
},
zod: {
input: {
target: "openapi.json", // OAS json
},
output: {
mode: "tags-split",
client: "zod", // zod
target: "./shared/api/endpoints",
fileExtension: ".zod.ts",
},
},
});
package.json 스크립트 추가
{
...
"scripts": {
...
"generate:api": "orval"
},
...
}
BE에서 OAS json 생성(NestJS 기준)
import { writeFileSync } from "node:fs";
import { join } from "node:path";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "../app.module";
import { setupSwagger } from "../config/document.config";
async function generateSwagger() {
const app = await NestFactory.create(AppModule, { logger: false, abortOnError: false });
const document = await setupSwagger(app);
const outputPath = join(process.cwd(), "..", "..", "docs", "api.json");
writeFileSync(outputPath, JSON.stringify(document, null, 2));
console.log(`Swagger JSON generated at: ${outputPath}`);
await app.close();
}
generateSwagger().catch(error => {
console.error("Error generating Swagger:", error);
process.exit(1);
});
Github Action을 이용한 API 생성 자동화
name: OpenAPI Generator
on:
pull_request:
types: [opened, synchronize]
paths:
- "apps/api/**"
branches:
- main
jobs:
generate-api:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup PNPM
uses: pnpm/action-setup@v2
with:
version: 9
- name: ⚙️ Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".node-version"
cache: "pnpm"
cache-dependency-path: "pnpm-lock.yaml"
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Generate documentation (api)
working-directory: apps/api
run: pnpm run generate:swagger
- name: Generate API using Orval
working-directory: apps/web
run: pnpm run generate:api
- name: Commit and push changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "feat(web): update API using oas [skip ci]"
commit_user_name: "frieeren[bot]"
commit_user_email: "frieeren[bot]@users.noreply.github.com"
branch: ${{ github.head_ref }}
push_options: --force-with-lease
PR

https://github.com/Frieeren/time-align/pull/19
개선된 개발 프로세스
인터페이스 작업을 협의한 이후에는 OAS JSON에서 출력된 API와 MSW mock API를 이용해서 웹 개발과 실제 API 구현을 병렬적으로 진행할 수 있습니다.

실무 팁
schema 수정이 필요하면?
transformer
import { defineConfig } from "orval"; export default defineConfig({ api: { input: { target: "openapi.json", // OAS json }, output: { override: { transformer: (verb: GeneratorVerbOptions): GeneratorVerbOptions => { if (verb.response?.definition.errors === "void") { verb.response.definition.errors = "null"; } return verb; }, }, }, }, };
custom fetch 수정이 필요하다면?
mutator
공식 예제를 참고하여 커스텀 HTTP 클라이언트를 구성할 수 있습니다.
느낀점
Orval을 도입함으로써 API 연동 프로세스의 반복되는 부분을 자동화하고 백엔드 API 개발과 프론트엔드 개발을 병렬로 진행하는데 도움을 주어 전체적인 개발 효율성이 크게 개선되었습니다. 특히 단순한 API 연동 작업은 1분 이내로 완료할 수 있게 되어, 개발자가 정말 중요한 비즈니스 로직과 사용자 경험 개선에 더 많은 시간을 투자할 수 있게 되었다고 생각합니다.
Reference
Last updated