개발 공부

OpenAPI만 주면 API 요청 함수를 생성해준다고?

gamzamandu 2026. 6. 7. 13:46

[서론]

[1. 시간이 없다 시간이]

졸업작품전 프로젝트를 제출해야 하고 남은 시간은 한 달. 코엑스 전시 건으로 한 주가 지나갔으니 2주 정도의 개발시간이 남았다.

네이버 페이 비상장을 레퍼런스로 한 "교내 프로젝트를 상장시키는 증권거래소, 깃허브 활동 내역이 뉴스가 된다!"를 혼자 만들어야 한다. 증권 시장의 시스템(IPO, 공모청약, 주식거래, 유동성)의 기능을 구현해야 했기 때문에 만들어야 할 것들도 많고 그만큼 프론트엔드에 연결해야 할 API도 많다.

좌(부산소마고 증권거래소), 우(네이버 페이 비상장)

 

그것을 해결해줄 한 가지의 빛을 보았으니 그것이 Orval이다.

[본론]

[1. Orval이 뭐예요??]

Orval은 OpenAPI 스펙을 통해 Typscript로 API함수, 타입, 목데이터까지 생성해 주는 라이브러리이다. 노가다성으로 반복되는 CRUD API 보일러플레이트를 생성해 주며, 무려 React Query 훅까지 생성해 준다. 

좌(OpenAPI 스펙)을 토대로 (우)보일러플레이트 코드를 생성하는 모습

[2. 본론 : 도입했을 때 어때]

[1. Pure UI 영역에 집중할 수 있다.]

Orval이 API 레이어를 책임져주다 보니, 컴포넌트 코드에는 오직 상태 관리와 가시적인 영역을 위한 코드만 남는다. API 호출 레이어가 자동 생성되면서, 컴포넌트에서는 UI와 상태 관리에 더 집중할 수 있다.

[2. 생산성이 압도적으로 올라간다.]

API 함수들을 직접 구현할 필요가 없어지다 보니 프론트의 주요 작업이 크게 날아간다. 직접 구현해야 할 영역은 낙관적 업데이트(그마저도 Orval의 함수로 구현하면 된다)와 SSE 정도다.

SSE나 WebSocket 같은 스트리밍 통신은 OpenAPI 표준으로 명확히 표현하기 어려워  Orval 측에서도 지원하지 않는 것 같다. Orval은 REST API 도구라고 생각하면 좋다.

 

[3. Orval에 종속되는 거 아니야?]

그렇지 않다. Orval은 "스펙 → 타입 + 훅" 변환기 한 단계일 뿐이고, 실질적인 제어권은 개발자한테 있다.

 

예를 들면 baseAPI를 오버라이딩해서 인프라 코드를 우리 손에 둘 수 있다. Orval은 기본적으로 단순 baseFetch만 쓰는데, config를 통해 이를 커스텀 함수로 교체할 수 있다.

// orval.config.ts
override: {
  mutator: { path: 'src/shared/api/index.ts', name: 'baseFetch' }
}

 

Orval이 코드를 생성할 때, 모든 fetch 호출부에 직접 fetch()를 박는 대신 이 경로의 baseFetch를 import해서 쓴다. 함수 위치와 이름만 정해주면 Orval이 그 시그니처에 맞춰 생성해 주는 식이다.

 

커스텀 훅이 필요하다면 생성된 훅을 컴포넌트에서 직접 쓰는 대신, 도메인 훅으로 한 번 더 감싸면 된다.

// src/features/admin-panel/model/useAdminStocksQuery.ts
export function usePendingStocksQuery() {
  return useListByStatusSuspense({ status: 'PENDING' }, { query: { refetchInterval: 30_000 } })
}

// src/features/stock-team/model/useAddTeamMember.ts
export function useAddTeamMember(ticker: string) {
  return useMutation({
    mutationFn: ({ userId, shareRatio }) => addTeamMember(ticker, { userId, shareRatio }),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: getGetMyStocksQueryKey() }),
  })
}

컴포넌트는 생성된 함수명과 시그니처를 직접 모른다. 도메인 훅 이름(usePendingStocksQuery, useAddTeamMember)만 안다. 생성기를 교체해도 도메인 훅 내부만 고치면 된다.

 

[4. Orval쓰면 API 코드 난잡한 거 아니야?]

그렇지 않다. 오히려 반대다. 내 프로젝트 백엔드의 API 엔드포인트는 총 124개였는데, 이것을 인간이 직접 관리하는 것이 더 불안할 수 있다. 사람이 124개의 API 함수를 직접 타이핑하다 보면 오탈자나 타입 실수가 섞여 들어오기 마련이다. Orval은 스펙에서 코드를 생성하기 때문에 오탈자가 끼어들 여지가 없고, 백엔드 구조가 그대로 매핑되기 때문에 별도로 파악할 필요도 없다. 

 

Orval이 생성한 코드도 체계적으로 관리하기 위해 생성 모드를 세 가지로 지원한다.

  1. single: 모든 API를 한 파일 또는 한 묶음으로 생성하는 방식
  2. tags: 태그 기준으로 파일을 나누되, 비교적 덜 세분화된 구조로 생성
  3. tags-split: 태그별로 디렉터리까지 분리해서 생성

내 프로젝트에서는 tags-split 모드를 사용했는데, 컨트롤러별 디렉터리로 격리된다.

src/api/
  ├── admin-stock-controller/
  ├── stock-controller/
  └── ...

백엔드 스펙 구조가 그대로 매핑되고, mode: 'tags-split' 옵션 하나만 바꾸면 구조 자체도 재배치할 수 있다. 

 

[5. API가 변경되면 어떻게 마이그레이션 하는데?]

Orval의 API 함수는 백엔드의 OpenAPI에 따라 자동으로 생성된다. API 스펙이 변경되면 타입 에러가 발생하기 때문에, 컴파일 단계에서 빠르게 문제를 확인할 수 있다.

 

[6. 목데이터도 연결하기 편하다.]

Orval는 mock: true 옵션 하나로 MSW 기반 mock 핸들러와 샘플 데이터를 자동 생성해 준다.

[결론]

2주라는 시간 안에 124개의 API를 연결하면서도 UI 품질을 유지할 수 있었던 건 Orval 덕분이었다. 새로운 도구를 빠르게 도입해 실행력을 확보하면서도, Orval이 종속되지 않는 구조를 제공해준 덕분에 확장성을 잃지 않았다.


FSD와의 궁합도 좋았다. Orval이 생성한 코드는 src/api/에 격리되고, 인프라 코드인 baseFetch는 FSD의 shared 레이어에 자연스럽게 안착했다. features의 도메인 훅이 api를 참조하는 방향도 FSD의 단방향 의존성 원칙과 맞아떨어졌다. 자동생성 코드와 수동 코드의 경계가 구조적으로 명확하게 나뉘었다.


백엔드 스펙이 그대로 코드로 매핑되다 보니 프론트와 백엔드 사이의 간극도 줄어들 것 같다. 이 프로젝트는 내가 모든 영역을 담당했기 때문에 직접 체감하진 못했지만, 실제 협업 상황이라면 API 명세가 변경될 때마다 타입 에러로 즉시 드러나기 때문에 별도 커뮤니케이션 없이도 변경점을 빠르게 파악할 수 있을 것이다.


Orval은 단순히 코드를 줄여주는 도구라기보단, API 레이어라는 인프라 관심사를 위임함으로써 개발자가 진짜 집중해야 할 영역에 더 많은 시간을 쓸 수 있게 해주는 도구라고 느꼈다.