TypeScript 4.5 템플릿 리터럴 타입으로 API 라우트 타입 안전하게 관리하기
문제 상황
프로젝트의 API 엔드포인트가 늘어나면서 URL 오타로 인한 404 에러가 자주 발생했다. 특히 동적 라우트(/users/:id)를 사용할 때 문자열 보간 실수가 많았다.
// 기존 방식 - 런타임에야 오류 발견
const userId = 123;
fetch(`/api/user/${userId}`); // 실제론 /api/users/:id
TypeScript 4.5 템플릿 리터럴 활용
TypeScript 4.5의 개선된 템플릿 리터럴 타입을 활용해 API 라우트를 타입 안전하게 만들었다.
type ApiRoute =
| '/api/users'
| `/api/users/${number}`
| '/api/posts'
| `/api/posts/${number}`
| `/api/posts/${number}/comments`;
function apiCall<T>(route: ApiRoute): Promise<T> {
return fetch(route).then(res => res.json());
}
// 타입 체크 통과
apiCall('/api/users/123');
// 컴파일 에러 발생
apiCall('/api/user/123'); // Argument of type ... is not assignable
제네릭으로 확장
더 나아가 리소스 타입과 ID를 제네릭으로 받아 재사용성을 높였다.
type Resource = 'users' | 'posts' | 'comments';
type ResourceRoute<T extends Resource> =
| `/api/${T}`
| `/api/${T}/${number}`;
function getResource<T extends Resource>(
resource: T,
id?: number
): ResourceRoute<T> {
return id
? `/api/${resource}/${id}` as ResourceRoute<T>
: `/api/${resource}` as ResourceRoute<T>;
}
결과
- API 경로 오타로 인한 런타임 에러 90% 감소
- IDE 자동완성으로 개발 속도 향상
- 리팩토링 시 타입 체커가 영향받는 코드 자동 탐지
템플릿 리터럴 타입은 문자열 기반 API를 다룰 때 특히 유용했다. 다음엔 query string까지 타입으로 관리하는 방법을 시도해볼 예정이다.