안녕하세요, 만타(Manta) 백엔드 엔지니어 전현성입니다. 이번 파트에서는 TypeScript 타입을 기반으로 API 문서화를 자동화해주는 라이브러리 Tspec의 개발 배경과 사용방법을 소개합니다.
1. 소개
Tspec이란?
API 문서화, 왜 필요할까?
API 문서화의 허점, 관리비용과 신뢰성
기존 솔루션 비교
새로운 솔루션, Tspec
2. Tspec 사용법
1) 프로젝트 설정
2) 스키마 정의
3) API 명세 정의
4) API 문서 생성
5) API 서버 연동 (Express.js)
6) API 테스트
3. 마치며
1. 소개
혹시 TypeScript로 REST API를 만들면서 “타입만으로 API 문서를 자동으로 만들 순 없을까?🤔”라는 생각을 해보신 적 없으신가요?
이러한 기대를 현실로 만들어 주는 라이브러리가 바로 Tspec입니다.
Tspec이란?
TypeScript 타입과 JSDoc을 기반으로 API 문서화를 자동화해주는 라이브러리입니다. 단 몇 줄의 코드 추가만으로 누구나 손쉽게 REST API를 문서화할 수 있도록 도와줍니다.
Demo 영상
사용법이 정말 간단하지 않나요?
Tspec의 자세한 사용방법을 본격적으로 소개 드리기에 앞서, API 문서화가 왜 필요하며 기존의 어떤 문제점을 해결하고자 Tspec을 새롭게 개발하게 되었는지 그 개발 배경을 말씀드리고자 합니다.
API 문서화, 왜 필요할까?
REST API를 만들다 보면 다른 개발자에게 API를 어떻게 공유할지, 어떻게 문서화할지 고민해 본 적 있지 않으신가요? 많은 개발자들은 효율적인 API 문서화를 위해 Swagger라고도 불리는 OpenAPI Specification(이하 OpenAPI Spec) 표준 명세를 사용하고 있습니다. OpenAPI Spec을 사용하면 API 문서 생성, 테스트, 버전 관리 등의 이점이 있기 때문입니다.
API 문서화의 허점, 관리비용과 신뢰성
API를 문서화하면 개발자 간 소통 비용이 줄어들고, 개발 생산성이 높아집니다. 누구나 아는 문서화의 장점입니다. 하지만 이를 위해 OpenAPI Spec을 작성하는 일은 매우 지루하고 귀찮은 작업입니다. 그걸 꾹 참고 인내하며 모든 API를 문서화했다고 치더라도 더 큰 문제는 그 이후에 있습니다.
API는 시간이 지나면서 추가, 제거, 수정되기 십상이기에 코드가 변함에 따라 API 문서 또한 최신화해야 합니다. 그렇지 않으면 API 문서는 점차 신뢰성을 잃고 더 이상 사용되지 않는 레거시가 될 수 있습니다.
리디에서도 이러한 문제를 피해 갈 수 없었습니다. 리디는 지난 10년간 서비스를 운영하면서 여러 개발자들을 거쳐 수많은 REST API들이 생겨났습니다. 해당 API들을 문서화하기 위해 리디에서는 OpenAPI Spec을 YAML 파일로 직접 관리하였지만, API의 수정이 생길 때마다 관련 OpenAPI Spec, TypeScript 타입, 그리고 관련 코드를 일일이 수정해야 하는 불편함이 있었습니다. 또한 수정 과정에서 OpenAPI 문법에 익숙하지 않아 스키마를 잘못 수정하거나 변경 사항이 누락될 경우, 문서의 신뢰도를 떨어뜨리기도 하였습니다.
결국 OpenAPI Spec을 직접 관리하는 문서화 방식은 API 수정에 따른 관리 부담이 크며, 수정 과정에서 실수가 발생하기 쉽기 때문에 문서의 신뢰성을 높게 유지하기 어렵다는 것을 알게 되었습니다.
기존 솔루션 비교
그렇다면 TypeScript 타입과 OpenAPI Spec 둘 중 하나를 SSoT(Single Source of Truth)로 사용하고 나머지를 자동으로 생성해 준다면, 관리 비용은 줄이면서 신뢰성은 높게 유지할 수 있지 않을까요?
SSoT를 고려한 기존의 솔루션들을 도입부터 관리까지 다양한 측면에서 비교해 보면 다음과 같습니다.
(첨부 정보는 2023-05-26 기준입니다.)
OpenAPI Spec ➡️ TypeScript | TypeScript ➡️ OpenAPI Spec | |
---|---|---|
프로젝트 |
– OpenAPI Generator – OpenAPI Typescript Codegen |
– NestJS – Tsoa |
사용성 | 1점: – 프로그래밍 언어 외 OpenAPI 문법 사용 – 자동완성 및 타입힌트 X |
2점: – TypeScript Class/Decorator 문법 사용 – 자동완성 및 타입힌트 O – typeof/generic 등 복잡한 타입 지원 X |
학습 비용 | 1점: – OpenAPI 문법 학습 필요 |
2점: – OpenAPI 문법 학습 필요 X – 단, 프레임워크 학습 필요 |
관리 비용 | 2점: – OpenAPI Spec 수정 시 타입 자동 최신화 – 생성된 타입 수정 불가 및 별도 관리 필요 |
3점: – 코드 수정 시 API 문서 자동 최신화 |
도입 비용 | 2점: – OpenAPI Spec만 있다면 그대로 사용 가능 – 없다면 모든 API를 OpenAPI로 정의 필요 |
1점: – 모든 코드를 프레임워크 구조에 맞춰야함 |
종합 점수 | 6 | 8 |
이외에도 GraphQL, gRPC, tRPC 등 처음부터 스키마를 지원하는 프로토콜을 사용하여 API를 문서화하는 방법도 있습니다. 하지만 이는 프레임워크를 도입하는 것과 마찬가지로 도입 비용이 크고 클라이언트 코드까지 모두 변경해야 한다는 단점이 있습니다.
단순히 API 문서화를 위해 특정 프레임워크나 프로토콜이 제안하는 구조에 맞추는 것은 주객전도적인 접근입니다. 특히 리디와 같이 이미 수년간 운영 중인 규모의 서비스에서는 해당 구조에 맞추는 일에 큰 추가 비용이 발생할 수 있습니다.
그렇다면 기존 프로젝트에 쉽게 도입할 수 있으면서 사용성, 학습 편의성, 관리 용이성까지 모두 겸비한 API 문서화 라이브러리는 없는 걸까요?
새로운 솔루션, Tspec
이러한 기대를 충족시키기 위해 고안된 라이브러리가 바로 Tspec입니다.
Tspec은 아래 네 가지 철학에 기반하여 TypeScript 타입을 기반으로 누구나 손쉽게 API를 문서화할 수 있도록 설계되었습니다.
- 쉬운 사용: TypeScript의 타입을 통해 스키마를 정의하고, JSDoc을 사용하여 코드에 주석을 추가하듯 문서화가 가능합니다.
- 쉬운 학습: OpenAPI 문법이나 특정 프레임워크의 사용법 등을 익힐 필요 없이 TypeScript 타입과 JSDoc만으로 OpenAPI Spec을 생성합니다.
- 쉬운 관리: 타입을 수정할 때마다 API 문서가 자동으로 최신화됩니다.
- 쉬운 도입: 기존의 코드는 그대로 둔 채로, 단 몇 줄의 코드 추가만으로 API를 손쉽게 문서화할 수 있습니다.
자, 그럼 예제 코드와 함께 Tspec의 사용방법에 대해 자세히 살펴보겠습니다.
2. Tspec 사용법
간단한 도서 정보 API를 문서화해 보면서 Tspec의 사용법에 대해 간략히 익혀보도록 하겠습니다.
예제에 사용된 코드는 여기에서 확인하세요.
1) 프로젝트 설정
먼저 새로운 TypeScript 프로젝트를 만들고, Tspec 패키지를 설치해 봅시다.
# 예제 프로젝트 폴더 생성
mkdir tspec-example
cd tspec-example
# pacakge.json 파일 생성
yarn init -y
# tsconfig.json 파일 생성
tsc --init
# Tspec 패키지 설치
yarn add tspec
# index.ts 파일 생성
touch index.ts
폴더 구조는 아래와 같습니다.
tspec-example
├── index.ts
├── package.json
├── tsconfig.json
└── node_modules
├── (dependencies)
└── ...
2) 스키마 정의
TypeScript 타입을 통해 API 응답에 사용될 도서 스키마를 index.ts
파일에 정의해 봅시다.
interface Book {
id: number;
title: string;
tags: Tag[];
}
type Tag = '로맨스' | '판타지';
이제 JSDoc을 사용하여 스키마에 대한 설명과 예시 정보를 덧붙여 봅시다.
import { Tspec } from "tspec";
/** 도서 정보 */
interface Book {
/** 도서 ID */
id: number;
/**
* 도서명
* @example 상수리 나무 아래
*/
title: string;
tags: Tag[];
}
/** 태그 정보 */
type Tag = '로맨스' | '판타지';
3) API 명세 정의
스키마 정의를 완료했다면 이제 API 명세를 정의해 봅시다.
export type BookApiSpec = Tspec.DefineApiSpec<{
tags: ['도서'],
paths: {
'/books/{id}': {
get: {
summary: '단일 도서 조회',
path: { id: number },
responses: { 200: Book },
},
},
}
}>;
tags
: API를 분류할 태그를 정의합니다.paths
: API의 URL과 HTTP method 별 명세를 정의합니다.summary
: API에 대한 간략한 설명을 적습니다.path
: HTTP 요청에 필요한 path 파라미터를 설정합니다. (query
및body
파라미터도 동일한 방식으로 설정 가능)responses
: HTTP 응답에 대한 타입을 코드별로 정의합니다.export
설정을 해주어야 Tspec에서 해당 명세를 OpenAPI Spec 생성 대상에 포함시킵니다.
4) API 문서 생성
벌써 API 문서화 작업이 끝났습니다! 이제 위에서 정의한 API 명세가 잘 문서화됐는지 확인해 볼까요?
yarn tspec serve
명령어를 통해 Tspec 서버를 실행시키고, http://localhost:7000/docs/에 접속합니다.
Tspec은 소스파일 내
Tspec.DefineApiSpec
로 정의한 타입을 자동으로 파싱하여 API 문서를 생성합니다.
Swagger UI를 통해 단일 도서 조회 API
가 잘 문서화된 것을 확인할 수 있습니다.
또한 스크롤을 밑으로 내리면 Book
과 Tag
스키마가 설명과 예시와 함께 잘 문서화된 것을 확인할 수 있습니다.
그렇다면 API 문서화에 사용된 OpenAPI Spec 파일이 실제로 어떻게 생겼는지 한 번 확인해 볼까요?
yarn tspec generate
명령어를 실행시켜 openapi.json
파일을 생성합니다.
글에서는 가독성을 위해 YAML 형식으로 표현하였습니다.
info:
title: Tspec API
version: 0.0.1
openapi: 3.0.3
paths:
"/books":
get:
operationId: BookApiSpec_get_/books
tags:
- 도서
summary: 단일 도서 조회
parameters:
- name: id
in: path
required: true
schema:
type: number
responses:
'200':
content:
application/json:
schema:
"$ref": "#/components/schemas/Book"
components:
schemas:
Book:
description: 도서 정보
type: object
properties:
id:
description: 도서 ID
type: number
title:
description: 도서명
example: 상수리 나무 아래
type: string
tags:
type: array
items:
"$ref": "#/components/schemas/Tag"
additionalProperties: false
required:
- id
- tags
- title
Tag:
description: 태그 정보
enum:
- 로맨스
- 판타지
type: string
TypeScript 타입만으로 이렇게 복잡한 OpenAPI Spec을 쉽게 뽑아낼 수 있다는 점이 정말 매력적이지 않나요?
5) API 서버 연동 (Express.js)
API 문서화가 잘 된 것을 확인했다면, 이제부터 Tspec을 사용하여 Express.js API를 문서화하는 방법에 대해 알아보도록 하겠습니다.
먼저, Express 패키지를 설치합니다.
yarn add express
yarn add -D @types/express
Express를 이용하여 단일 도서 조회 API
를 작성해 봅시다.
import express, { Request, Response } from "express";
/** Book 타입 정의 생략 */
const getBookById = (req: Request<{ id: string }>, res: Response<Book>) => {
res.json({
id: +req.params.id,
title: '상수리 나무 아래',
tags: ['로맨스', '판타지'],
});
}
const router = Router().get('/books/:id', getBookById);
Path parameter로 id
를 전달받아 도서 정보를 내려주는 getBookById
컨트롤러를 정의하고, 해당 함수를 GET /books/:id
경로에 등록하였습니다.
만약 위처럼 Request
및 Response
타입을 미리 잘 정의해두었다면, 컨트롤러를 handler
필드로 전달하여 path
, query
, body
, responses
명세를 자동으로 채워 넣을 수 있습니다.
export type BookApiSpec = Tspec.DefineApiSpec<{
tags: ['도서'],
paths: {
'/books/{id}': {
get: { summary: '단일 도서 조회', handler: typeof getBookById },
},
}
}>;
이제 컨트롤러를 문서화의 SSoT로 사용할 수 있기 때문에 API 수정에 따른 문서 관리 부담이 훨씬 적어졌습니다!
다음으로, API 문서를 라우터에 등록하기 위해 TspecDocsMiddleware
를 Express 앱에 등록합니다.
import { Tspec, TspecDocsMiddleware } from "tspec";
import express, { Request, Response, Router } from "express";
/** 생략 */
const initServer = async () => {
const app = express();
app.use(router);
app.use('/docs', await TspecDocsMiddleware());
app.listen(3000, () => {
console.log(`Example app listening at http://localhost:3000`);
});
}
initServer();
서버를 3000번 포트로 열고, GET /docs
경로에 API 문서를 등록하였습니다.
이제 모든 준비가 끝났습니다. ts-node index.ts
명령어를 통해 Express 서버를 실행시키고, http://localhost:3000/docs에 접속합니다.
API 문서화가 성공적으로 이뤄진 것을 확인할 수 있습니다.
6) API 테스트
API 문서화를 마쳤다면, 이제 API가 정상 동작하는지 테스트해볼까요?
SwaggerUI는 개발 중인 API를 쉽게 테스트할 수 있도록 Try it out 기능을 제공합니다.
Try it out 버튼을 눌러 API가 의도대로 동작하는지 테스트해 봅시다.
id
값에 아무 숫자를 입력한 뒤 Execute 버튼을 눌러줍니다.
id
값에 12345
를 실어 요청을 보냈고, 응답에서 id
값이 12345
로 잘 내려온 것을 통해 API가 정상 동작했다는 것을 확인할 수 있습니다.
드디어, Tspec 사용법에 대한 소개가 끝났습니다.
분량상 Tspec의 모든 사용법에 대해서 적지는 못하였지만 인증(Authorization)을 포함한 더 자세한 Tspec 사용방법이 궁금하시다면 공식 문서에서 참고해 주시기 바랍니다.
3. 마치며
현재 리디의 글로벌 웹툰 서비스인 만타(Manta)는 Tspec을 성공적으로 도입했으며, 그 결과 API 문서화에 필요한 관리 비용을 획기적으로 줄였습니다.
TypeScript 타입을 그대로 활용하면서 단 몇 줄의 코드 추가만으로 쉽게 API를 문서화하고 테스트할 수 있다는 점이 정말 매력적이지 않나요? API 문서화를 고민하는 TypeScript 개발자라면 Tspec 도입을 고려해 보시는 것은 어떨까요.
긴 글 읽어주셔서 감사합니다.
고객과 발맞춰 새로운 콘텐츠 경험을 선보이는
리디와 함께할 당신을 기다립니다.