TypeScript 4.5 템플릿 리터럴 타입으로 API 라우트 검증하기
문제 상황
프로젝트에서 API 엔드포인트를 문자열로 관리하다 보니 오타로 인한 런타임 에러가 자주 발생했다. /api/users/:id 같은 동적 라우트를 처리할 때 특히 문제였다.
// 기존 방식 - 타입 검증 없음
const userId = 123;
fetch(`/api/user/${userId}`); // users가 아닌 user로 오타
템플릿 리터럴 타입 활용
TypeScript 4.5에서 템플릿 리터럴 타입의 타입 추론이 개선되어 이를 활용한 해결책을 구현했다.
type ApiRoute =
| '/api/users'
| '/api/users/${number}'
| '/api/posts'
| '/api/posts/${number}/comments';
function apiCall<T extends ApiRoute>(route: T, options?: RequestInit) {
return fetch(route, options);
}
// 타입 체크 통과
apiCall(`/api/users/${123}`);
// 컴파일 에러
apiCall(`/api/user/${123}`);
동적 파라미터 헬퍼
더 나아가 파라미터를 받는 헬퍼 함수를 만들어 사용성을 개선했다.
type RouteParams<T extends string> =
T extends `${infer Start}\${number}${infer Rest}`
? [number, ...RouteParams<Rest>]
: [];
function buildRoute<T extends ApiRoute>(
template: T,
...params: RouteParams<T>
): string {
let route = template as string;
params.forEach(param => {
route = route.replace(/\${number}/, String(param));
});
return route;
}
const route = buildRoute('/api/posts/${number}/comments', 42);
// '/api/posts/42/comments'
결과
컴파일 타임에 API 라우트 오타를 잡을 수 있게 되어 런타임 에러가 크게 줄었다. IDE의 자동완성도 정확해져 개발 경험이 개선되었다. 다만 템플릿 리터럴 타입이 복잡해지면 컴파일 속도가 느려지는 점은 주의가 필요하다.