본문 바로가기

Engineering/Frontend

Swagger에서 API 클라이언트 자동생성까지 — orval + TanStack Query 도입기

#orval #TanStack Query #React Query #TypeScript #Next.js #API 자동생성
"API 연동 코드를 더 이상 직접 작성하지 않아도 된다면?"

프론트엔드 개발을 하다 보면 백엔드 API가 변경될 때마다 타입, fetch 함수, 쿼리 훅을 하나하나 수정해야 하는 번거로움이 생깁니다. 이 글에서는 orval로 Swagger 문서 기반 TypeScript 클라이언트를 자동생성하고, TanStack Query와 함께 연동하는 방법을 정리합니다.


1. 왜 orval인가?

orval은 OpenAPI(Swagger) 스펙 파일을 읽어서 다음을 자동으로 생성합니다.

  • TypeScript 타입 (Request/Response DTO 포함)
  • API 호출 함수 (axios 또는 fetch 기반)
  • TanStack Query 훅 (useQuery, useMutation 래퍼)

직접 타입을 정의하고 훅을 만드는 반복 작업이 사라지기 때문에, API 스펙이 변경돼도 orval 명령 한 번으로 클라이언트 코드를 동기화할 수 있습니다.


2. 설치 및 기본 설정

npm install -D orval
npm install @tanstack/react-query axios

프로젝트 루트에 orval.config.ts를 생성합니다.

// orval.config.ts
import { defineConfig } from 'orval'

export default defineConfig({
  myApi: {
    input: {
      target: './swagger.json', // 또는 'http://localhost:8080/api-docs/swagger.json'
    },
    output: {
      mode: 'tags-split',       // 태그별로 파일 분리
      target: './src/api/generated',
      schemas: './src/api/model',
      client: 'react-query',    // TanStack Query 훅 생성
      httpClient: 'axios',
      override: {
        mutator: {
          path: './src/lib/axiosInstance.ts',
          name: 'axiosInstance',
        },
      },
    },
  },
})

3. Axios 인스턴스 커스터마이징

orval이 생성한 코드는 mutator로 지정한 axios 인스턴스를 사용합니다. 여기서 인터셉터, 인증 토큰 처리 등을 중앙 관리할 수 있습니다.

// src/lib/axiosInstance.ts
import axios from 'axios'

const instance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  timeout: 10000,
})

// 요청 인터셉터 — 액세스 토큰 자동 첨부
instance.interceptors.request.use((config) => {
  const token = localStorage.getItem('accessToken')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

// 응답 인터셉터 — 401 처리
instance.interceptors.response.use(
  (res) => res,
  async (error) => {
    if (error.response?.status === 401) {
      // 토큰 갱신 로직
    }
    return Promise.reject(error)
  }
)

export const axiosInstance = <T>(config: Parameters<typeof instance>[0]) =>
  instance.request<T>(config).then((res) => res.data)

4. 코드 생성

npx orval

실행하면 src/api/generated/ 아래에 태그별 파일이 생성됩니다.

src/api/generated/
├── users/
│   ├── users.ts         ← useQuery, useMutation 훅
│   └── users.msw.ts     ← MSW 목업 (선택)
├── products/
│   └── products.ts
src/api/model/
├── createUserDto.ts
├── userResponse.ts
└── ...

5. 생성된 훅 사용 예시

useQuery — 데이터 조회

// src/components/UserProfile.tsx
import { useGetUsersId } from '@/api/generated/users/users'

export function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading, isError } = useGetUsersId(userId, {
    query: {
      staleTime: 1000 * 60 * 5, // 5분
      enabled: !!userId,
    },
  })

  if (isLoading) return <Skeleton />
  if (isError) return <ErrorMessage />

  return <div>{data?.name}</div>
}

useMutation — 데이터 변경

// src/components/CreateUserForm.tsx
import { usePostUsers } from '@/api/generated/users/users'
import { useQueryClient } from '@tanstack/react-query'
import { getGetUsersQueryKey } from '@/api/generated/users/users'

export function CreateUserForm() {
  const queryClient = useQueryClient()

  const { mutate, isPending } = usePostUsers({
    mutation: {
      onSuccess: () => {
        // 유저 목록 캐시 무효화
        queryClient.invalidateQueries({ queryKey: getGetUsersQueryKey() })
      },
    },
  })

  const handleSubmit = (data: CreateUserDto) => {
    mutate({ data })
  }

  return (
    <form onSubmit={...}>
      {/* 폼 내용 */}
      <button disabled={isPending}>
        {isPending ? '저장 중...' : '저장'}
      </button>
    </form>
  )
}

6. package.json 스크립트 등록

{
  "scripts": {
    "api:generate": "orval",
    "api:generate:watch": "orval --watch"
  }
}

개발 서버를 띄운 상태에서 --watch 모드를 사용하면 swagger.json이 변경될 때 자동으로 재생성됩니다.


7. MSW와 함께 목업 테스트

orval은 *.msw.ts 파일도 함께 생성합니다. Jest나 Storybook 환경에서 실제 API 없이 개발하거나 테스트할 때 유용합니다.

// src/mocks/handlers.ts
import { getUsersMock } from '@/api/generated/users/users.msw'

export const handlers = [
  ...getUsersMock(),
]

정리

항목 기존 방식 orval 도입 후
타입 정의 수동 작성 자동 생성
API 함수 수동 작성 자동 생성
TanStack Query 훅 수동 작성 자동 생성
API 변경 대응 전체 수동 수정 npm run api:generate 한 번

orval 도입 이후 API 연동 관련 보일러플레이트 코드가 크게 줄었고, 타입 불일치로 인한 런타임 오류도 줄어들었습니다. 특히 백엔드 팀과 스펙을 Swagger로 먼저 합의하고 개발을 병렬로 진행하는 방식이 가능해져서 협업 속도도 눈에 띄게 빨라졌습니다.

다음 글에서는 orval과 함께 Zod validation을 연동하는 방법을 다뤄볼 예정입니다.