mimizae 님의 블로그
swagger-typescript-api 적용기 본문
37기 DIVE SOPT에서 진행한 프로젝트 CareNA 프로젝트의 초기세팅 과정을 담은 글입니다! 🙌🏻

💡 swagger-typescript-api란?
Swagger 문서를 이용한 자동 타입 생성기이다!!
swagger-typescript-api는 OpenAPI(Swagger) 스펙으로부터 TypeScript 코드를 자동으로 생성해주는 도구다. 이 도구는 OpenAPI 3.0 및 2.0, JSON/YAML 형식의 스펙을 받아서 fetch나 axios 기반의 API 클라이언트 코드와 TypeScript 타입 정의를 생성할 수 있다.
타입만 생성해주는 줄 알았는데 클라이언트 코드까지 자동 생성해준다… 덜덜 😳😳
🤔 swagger-typescript-api를 사용하게 된 계기
OpenAPI(Swagger) 스펙을 기반으로 프론트엔드에서 사용할 API 타입과 호출 코드를 효율적으로 관리할 방법을 고민하던 중, swagger-typescript-api라는 도구가 있다는 것을 알게 되었다.
이 도구는 Swagger 스펙으로부터 TypeScript 타입뿐만 아니라 Axios 기반의 API 클라이언트 코드까지 함께 생성해준다는 점에서 프론트엔드 개발에 실질적인 도움이 될 수 있다고 판단했다.
기존에도 OpenAPI Generator, orval, openapi-typescript 등 다양한 대안이 존재하긴 했다. 각각의 대안을 판단하기에, OpenAPI Generator는 생성 코드가 비교적 복잡하고 프론트엔드에서 직접 다루기에는 부담이 있었으며
orval은 Tanstack Query 기반의 훅 생성에 강점이 있는 도구였지만, 본 프로젝트에서는 Tanstack Query 사용 여부와 관계없이 공통으로 활용 가능한 API SDK를 구성하는 것이 더 적합하다고 판단했다!!!!
openapi-typescript의 경우 타입 정의만 제공해 주기 때문에 API 호출 로직을 직접 구현해야 했다. 물론 구현은 가능했지만, OpenAPI 도구를 사용하는 만큼 API 호출 코드까지 함께 자동화할 수 있다면 개발 과정이 더 수월해질 것이라고 생각했다…!!!!!
이러한 점들을 고려했을 때 swagger-typescript-api는 프론트엔드에서 바로 사용할 수 있는 API SDK 형태의 코드를 생성하면서도, 생성 결과가 비교적 단순하고 가독성이 좋아 유지보수가 용이하다는 장점이 있었기 때문에 swagger-typescript-api 을 선택했다.
🚨 ky에서 axios로 변경
그래서 swagger-typescript-api를 설치하고 적용하려고 했는데, 하나 문제점이 있었다… 🤕
swagger-typescript-api가 공식적으로 ky를 지원 안 한다는 사실…!!!!!!
우리 서비스는 api 인터셉터로 자주 쓰던 axios 대신 ky를 선택했다. 그 이유는 ky가 axios와 세팅, 사용 방법이 거의 비슷해 적응하는 데 (axios를 알고 있는 상태라면) 러닝 커브가 굉장히 완만하고, ky가 axios보다 의존성이 가볍고 번들 사이즈가 작아지기 때문이었다.
하지만!! axios를 사용하더라도 번들 사이즈 증가로 인해 체감할 만한 초기 로딩 속도 차이는 크지 않다고 판단했다. 또한 swagger-typescript-api가 axios 기반의 API 클라이언트를 기본적으로 제공하고 있어, 별도의 어댑터를 구성하지 않고도 도구를 바로 활용할 수 있다는 점에서 개발 편의성이 더 높다고 보았다.
따라서 최종적으로 axios 사용 결정!!
⚙️ swagger-typescript-api 사용법
1. 설치
pnpm add swagger-typescript-api
2. package.json에 실행 커맨드 추가
"scripts": {
...
"api:generate": "swagger-typescript-api generate -p {Swagger 문서 Path} -o src/shared/apis/generated --no-client --modular -d --extract-request-body --extract-response-body --extract-response-error"
},
⬇️ 위의 명령어 및 자주 쓰이는 명령어 살펴보기 ⬇️
generate
- swagger-typescript-api의 코드 생성 서브커맨드
- Swagger 스펙을 읽어서 TS 코드를 만들어 줌
-p {Swagger 문서 Path}
- Swagger/OpenAPI 문서 위치, 이 문서를 기준으로 모든 타입이 만들어진다.
- 로컬 파일
-p ./swagger.json - URL
- -p https://api.example.com/v3/api-docs
- 로컬 파일
-o src/shared/apis/generated
- 생성 결과가 들어갈 디렉토리
- 보통, generated 폴더는 직접 수정 안 하는 영역
- git에 포함시키거나, CI에서 매번 생성
--no-client
- axios / fetch 기반 API 호출 함수 생성 안 함
- 대신 타입만 생성!!
- Request body
- Response body
- Error response
- Params, Query, Path 변수 등
-modular
- API 코드를 Swagger의 tag(컨트롤러) 단위로 분리하여 생성
- 하나의 거대한 파일이 아닌, 도메인별 파일 구조를 만든다.
generated/
├─ auth.ts
├─ user.ts
├─ product.ts
d (-default-response)
- Swagger 문서에 여러 response(200, 400, 500 등)가 정의되어 있을 경우 기본 response 타입을 자동으로 선택하도록 하는 옵션
- 일반적으로 200 또는 default response를 기준으로 삼는다.
→ Swagger 문서의 응답 정의가 불완전하거나 일관되지 않은 경우 성공 응답 타입을 안정적으로 사용하기 위해 필요한 옵션이다!
-extract-request-body
- request body를 별도의 TypeScript 타입으로 분리하여 생성
export interface CreateUserRequest {
name:string;
age:number;
}
-extract-response-body
- response body를 별도의 TypeScript 타입으로 분리하여 생성
export interface CreateUserResponse {
id:number;
}
-extract-response-error
- 에러 응답(4xx, 5xx)을 에러 전용 타입으로 분리하여 생성
export interface ErrorResponse {
message:string;
code:string;
}
-axios
- API 호출 코드를 axios 기반으로 생성
- fetch가 아닌 axios를 사용하는 프로젝트에 적합
n {name} (name)
- 생성되는 타입 / 클래스 / 함수에 사용될 기본 이름(prefix 또는 root name)을 지정한다.
- 팀 내 컨벤션에 맞게 수정 가능
우리 프로젝트에서는 API 인터셉터 설정, 요청 래핑 함수, 응답 코드 및 에러 처리 로직을 프론트엔드에서 별도로 관리하고 있다!!
따라서 swagger-typescript-api는 API 호출 로직을 생성하지 않고, 타입 정의만 생성하는 용도로 사용한다.
위의 실행 커맨드를 설정하고 실행한다면, 실제로 만들어지는 파일 예시이다!
generated/
├─ User.ts ← tag: User
├─ Auth.ts ← tag: Auth
├─ Product.ts ← tag: Product
├─ data-contracts.ts ← 전역 공통 (1개)
└─ index.ts ← 전역 barrel (1개)
❓data-contracts.ts
data-contracts.ts는 Swagger(OpenAPI) 문서의 components.schemas를 기반으로 생성되는 전역 공통 타입 파일이다.
여러 API에서 공통으로 사용될 수 있는 DTO, enum, 에러 타입, 공용 응답 모델 등이 이 파일에 정의되며, 도메인(tag)별로 분리되지 않고 한 번만 생성된다.
자동 생성 파일이므로 직접 수정하지 않아야 하며, 필요 시 각 도메인 타입 파일에서 참조되는 용도로만 사용한다!!
// data-contracts.ts
export interface BaseResponse<T> {
data: T;
message: string;
status: number;
}
export interface ErrorResponse {
message: string;
code: string;
}
❓index.ts
index.ts는 generated 디렉토리의 barrel 파일(entry point) 역할을 한다.
각 도메인(tag)별로 생성된 타입 파일과 공통 타입 파일을 한 곳에서 re-export하여, 외부에서는 단일 경로로 타입을 import 할 수 있도록 한다!!
프로젝트 정책에 따라 data-contracts.ts를 export 대상에서 제외하는 것도 가능하다.
// index.ts
export * from './User';
export * from './Auth';
export * from './data-contracts';
// 사용 예시
import {
LoginRequest,
LoginResponse,
} from '@/shared/apis/generated'; // 단일 경로로 타입 import!!
⬇️ Barrel 방식이란? ⬇️
Barrel 방식은 여러 파일에서 export되는 모듈들을 하나의 파일(index.ts)로 모아 다시 export하는 구조를 말한다.
이를 통해 외부에서는 개별 파일 경로를 직접 참조하지 않고, 단일 진입점(entry point)을 통해 필요한 모듈을 import할 수 있다.
Barrel 방식을 사용하면 import 경로가 단순해지고, 파일 구조 변경 시 영향 범위를 최소화할 수 있다!!!
Barrel 파일(index.ts)
// generated/index.ts
export * from './User';
export * from './Auth';
export * from './data-contracts';
사용 측 코드
import { LoginRequest, LoginResponse} from '@/shared/apis/generated';
(비교) Barrel 방식 미사용 시
import { LoginRequest } from '@/shared/apis/generated/Auth';
import { LoginResponse } from '@/shared/apis/generated/Auth';
💪🏻 실제 적용


현재 모든 타입이 data-contracts.ts 하나의 파일로만 생성되고 있다. . . ☠️
--modular 옵션을 사용하고 있음에도 파일이 분리되지 않고, index.ts 역시 생성되지 않았다.
원인을 확인해보니 Swagger 문서에서 각 API operation(GET, POST 등)에 tags가 지정되어 있지 않았다.
swagger-typescript-api의 --modular 옵션은 operation에 선언된 tags를 기준으로 파일을 분리하기 때문에, tag가 없는 경우 모든 타입을 공통 계약 파일인 data-contracts.ts로만 생성한다. . .!!!!
각 API operation에 도메인에 맞는 tags를 추가하도록 백엔드 측에 요청해야겠다. 😳😳
🌀 ETC
'FE' 카테고리의 다른 글
| React Route 초기 세팅 (0) | 2026.02.02 |
|---|---|
| Tanstack Query 초기 세팅 (0) | 2026.01.29 |
| pnpm이란? (0) | 2026.01.02 |
| 에러 핸들링을 하는 방법(Suspense와 ErrorBoundary) (0) | 2026.01.02 |
| CI/CD란 뭘까? (0) | 2025.12.26 |