"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을 연동하는 방법을 다뤄볼 예정입니다.
'Engineering > Frontend' 카테고리의 다른 글
| React의 <ViewTransition>으로 페이지 전환 애니메이션 만들어보기 (0) | 2026.03.08 |
|---|---|
| React Compiler v1.0과 Next.js 16 Cache Components가 바꾸는 2026 프론트엔드 개발 패러다임 (0) | 2026.03.06 |
| 타입 중복 없애는 TypeScript 유틸리티 타입 정리 (0) | 2026.02.21 |
| 2026년 프론트엔드, '서버 중심 리액트'와 'AI 동료'가 표준이 되다 (0) | 2026.01.18 |
| React 19 도입 1년, 이제 useMemo와 useCallback은 놓아줄 때가 됐다 (3) | 2026.01.10 |