↓ 본문으로 건너뛰기

API Development Orval OpenAPI

Orval 사용법: OpenAPI 스펙에서 타입이 잡힌 API 클라이언트와 목(mock) 생성하기

Orval은 OpenAPI 스펙을 읽어 타입이 잡힌 TypeScript 클라이언트와 React Query 훅, MSW 목을 생성해 줍니다. API 계층을 손으로 짤 일이 없어지죠. 설정 방법, 목이 동작하는 방식, 그리고 openapi-generator, openapi-typescript, Hey API와의 비교까지 정리했습니다.

Orval 사용법: OpenAPI 스펙에서 타입이 잡힌 API 클라이언트와 목(mock) 생성하기
OpenApi - 이 글은 시리즈의 일부입니다.
부분 2: 이 글

Orval은 OpenAPI(Swagger) 스펙을 읽어서 프론트엔드의 API 계층을 대신 써 주는 코드 생성기입니다. 엔드포인트마다 타입이 잡힌 함수나 React Query 훅, 계약과 정확히 일치하는 TypeScript 모델, 그리고 원하면 Faker 데이터로 채운 MSW 목 핸들러까지 만들어 줍니다. 설정 파일 하나를 커밋하고 orval을 실행하면 되고, 스펙이 바뀔 때마다 다시 실행하면 됩니다.

이 시리즈의 첫 글에서 “코드를 쓰기 전에 OpenAPI 계약부터 합의하자"는 이야기를 했습니다. 이번 글은 그 계약이 실제로 본전을 뽑게 해주는 도구에 대한 이야기입니다.

프론트엔드의 API 계층을 손으로 짜는 건, 즐겁다고 말하는 사람을 본 적이 없는 종류의 일입니다. 백엔드 문서에서 타입을 베껴 적고, 마흔 번째 똑같은 요청 래퍼를 쓰고, 3주 뒤에 누군가 필드명을 바꾸면 호출의 절반이 조용히 깨지죠. Orval은 이 일 자체를 없애줍니다. OpenAPI 스펙을 가리키기만 하면 클라이언트 코드를 생성해줍니다. 모든 엔드포인트에 대한 타입이 잡힌 함수, 계약과 정확히 일치하는 모델, 그리고 백엔드가 존재하기도 전에 개발에 쓸 수 있는 목(mock)까지.

프론트엔드 팀이 좋아하는 이유
#

목 생성이 은근히 저평가된 부분입니다. 스펙만 합의되면 프론트엔드는 가짜 서버를 띄워놓고 UI 전체를 거기에 대고 만들 수 있습니다. 영원히 “거의 다 됐어요” 상태인 엔드포인트를 기다릴 필요가 없죠. 진짜 백엔드가 나오면 클라이언트의 방향만 돌려주면 대부분 그대로 동작합니다. 양쪽이 같은 문서에서 만들어졌으니까요.

또 하나의 수확은, 지루한 코드가 구조적으로 올바르게 나온다는 점입니다. 손으로 짠 요청 바디에서 필드명을 잘못 치는 사람이 없어집니다. 애초에 손으로 요청 바디를 짜는 사람이 없어졌으니까요.

백엔드 팀도 좋아하는 이유
#

스펙이 단일 진실 공급원이 되면 일상적인 대화가 달라집니다. “이 엔드포인트가 뭘 반환하더라?“라는 채팅 스레드 대신, 프론트엔드는 생성된 타입을 읽으면 됩니다. 백엔드 개발자는 덜 끊기고, 피드백은 더 빨리 받습니다. 프론트엔드가 목을 상대로 만들어보다가 API가 어딘가 어색하다고 느끼면, 그걸 알게 되는 시점이 프로덕션 이후가 아니라 아직 고치기 싼 시점입니다.

우리 워크플로에 녹이는 방법
#

별다른 건 없습니다. 백엔드가 스펙을 제안하고, 양 팀이 리뷰하고, 프론트엔드는 Orval로 클라이언트와 목을 생성해 개발을 시작하고, 백엔드는 같은 계약에 맞춰 실제 엔드포인트를 구현합니다. 스펙이 바뀌면 양쪽에서 다시 생성하고, 뭐가 깨졌는지는 컴파일러가 정확히 알려줍니다. 사실 이 마지막 부분이 핵심입니다. 프론트와 백엔드 사이의 어긋남이 프로덕션 버그가 아니라 컴파일 에러가 되는 거죠.

설정하기
#

개발 의존성으로 설치하고, 생성된 코드가 import할 패키지도 함께 설치합니다. React Query 훅과 목을 쓸 거라면 TanStack Query, MSW, Faker입니다.

npm install --save-dev orval msw @faker-js/faker
npm install @tanstack/react-query

그다음 프로젝트 루트에 orval.config.ts를 추가합니다. 아래 설정은 Orval 공식 문서의 예제와 거의 같습니다.

import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    input: {
      target: './openapi.yaml', // a path or a URL
    },
    output: {
      mode: 'single',
      target: './src/api/petstore.ts',
      schemas: './src/api/model',
      client: 'react-query',
      httpClient: 'fetch',
      mock: true, // MSW handlers plus Faker data
      override: {
        fetch: { includeHttpResponseReturnType: false },
      },
    },
  },
});

무엇이 생성될지는 client가 정합니다. 그냥 fetch나 axios 함수, React Query·Vue Query·Svelte Query·SWR용 훅, Angular 서비스, Zod 스키마 중에서 고를 수 있습니다. includeHttpResponseReturnType: false 줄은 fetch 클라이언트가 { data, status } 대신 응답 바디 자체를 반환하게 하는데, 컴포넌트 입장에서는 대개 이쪽이 편합니다.

명령어를 아무도 외울 필요가 없도록 스크립트도 추가해 둡니다.

{
  "scripts": {
    "api": "orval"
  }
}

npm run api를 실행하면 훅과 모델이 생성됩니다. 컴포넌트에서는 여느 React Query 훅처럼 쓰면 되고, 타입은 이미 다 채워져 있습니다.

import { useListPets } from './api/petstore';

export function PetList() {
  const { data: pets, isLoading } = useListPets();
  if (isLoading) return <p>Loading…</p>;
  return (
    <ul>
      {pets?.map((pet) => <li key={pet.id}>{pet.name}</li>)}
    </ul>
  );
}

스펙에서 필드 이름을 바꾸고 npm run api를 다시 돌리면, pet.name을 쓰는 곳마다 빨간 줄이 그어집니다. 앞에서 말한 어긋남 검사가 바로 이것이고, 돌리는 데 드는 비용은 0입니다.

목을 상대로 개발하기
#

mock: true를 켜면 Orval은 엔드포인트마다 MSW 요청 핸들러도 만듭니다. 응답 스키마 모양 그대로의 Faker 데이터를 돌려주는 핸들러들이고, 이걸 전부 모아 반환하는 함수도 하나 생깁니다. 그 목록을 브라우저에서는 MSW의 setupWorker에, 테스트에서는 setupServer에 넘기면, 앱은 이미 계약과 일치하는 가짜 백엔드를 상대로 돌아갑니다. 핸들러마다 오버라이드도 받을 수 있어서, 특정 페이로드나 빈 목록, 에러 상태를 돌려주는 테스트를 누가 픽스처를 손으로 짜지 않아도 만들 수 있습니다.

프론트엔드가 첫날부터 작업을 시작할 수 있는 건 이 덕분입니다. 목은 누군가 따로 관리해야 하는 별도의 산출물이 아닙니다. 클라이언트와 같은 스펙에서 매번 다시 생성되니, 스펙보다 뒤처질 수가 없죠.

Orval과 openapi-generator, openapi-typescript, Hey API 비교
#

넷 다 OpenAPI 스펙에서 출발하지만, 멈추는 지점이 다릅니다.

생성하는 것잘 맞는 경우
Orval타입이 잡힌 클라이언트 함수나 프레임워크 훅(React Query, SWR, Vue, Svelte, Angular), 모델, MSW 목, Zod 스키마설정 하나로 훅과 목을 같이 뽑고 싶은 TypeScript 프론트엔드
openapi-generator수십 개 언어의 클라이언트와 서버 스텁(TypeScript 포함)같은 스펙을 Java, Kotlin, Swift, TypeScript로 모두 뽑아야 하는 팀
openapi-typescript타입만. 작은 타입 fetch 래퍼인 openapi-fetch와 짝을 이룸런타임 코드는 거의 없이 타입만 원할 때
Hey API (@hey-api/openapi-ts)타입이 잡힌 SDK. TanStack Query, Zod 등을 위한 플러그인 제공Orval과 비슷한 영역을 플러그인 시스템 중심으로 풀어낸 도구

타입만 필요하다면 openapi-typescript가 가장 가볍고, 딱히 반박할 거리도 없습니다. openapi-generator는 스펙 하나로 여러 언어를 먹여 살려야 할 때 제값을 합니다. TypeScript 출력도 동작은 하지만, 읽다 보면 Java를 먼저 염두에 두고 만든 티가 납니다. Orval과 Hey API 사이에서는 대체로 취향 문제이고, 어느 쪽이 내놓는 코드를 읽는 게 더 편한지로 갈립니다. 제가 계속 Orval을 쓰는 건 목이 훅과 같은 실행에서 함께 나오기 때문이고, 두 팀이 일하는 방식을 바꾼 게 바로 그 기능이었습니다.

화려한 도구는 아닙니다. 그저 팀 사이의 말다툼 한 종류를 통째로 조용히 없애줄 뿐인데, 저는 그 거래라면 언제든 응할 생각입니다.

OpenApi - 이 글은 시리즈의 일부입니다.
부분 2: 이 글