TypeScript 4.7 template literal type으로 API 라우트 타입 안전하게 관리하기
문제 상황
프로젝트에서 API 엔드포인트를 호출할 때 경로를 하드코딩하다 보니 오타로 인한 런타임 에러가 자주 발생했다. /api/users/:id 같은 동적 경로의 경우 파라미터 타입도 보장되지 않았다.
TypeScript 4.7 template literal type 개선
4.7 버전에서 template literal type의 타입 추론이 크게 개선되었다. 이를 활용해 API 라우트를 타입 안전하게 만들 수 있었다.
type ApiRoute =
| '/api/users'
| `/api/users/${string}`
| '/api/posts'
| `/api/posts/${string}`;
type ExtractParams<T extends string> =
T extends `${string}/${infer Param}`
? Param extends `${infer P}/${infer Rest}`
? P | ExtractParams<Rest>
: Param
: never;
function apiCall<T extends ApiRoute>(
route: T,
params: ExtractParams<T> extends string ? { id: string } : void
) {
// 실제 API 호출 로직
}
// 타입 안전한 호출
apiCall('/api/users/123', { id: '123' }); // OK
apiCall('/api/users'); // OK
apiCall('/api/users/123'); // params 필요 - 에러
실전 적용
프로젝트에서는 좀 더 실용적으로 접근했다.
const API_ROUTES = {
users: {
list: '/api/users',
detail: (id: string) => `/api/users/${id}` as const,
},
posts: {
list: '/api/posts',
detail: (id: string) => `/api/posts/${id}` as const,
},
} as const;
type ApiClient = {
get<T>(url: string): Promise<T>;
};
const client: ApiClient = axios.create();
// 사용
const userId = '123';
const user = await client.get(API_ROUTES.users.detail(userId));
함수로 감싸면서 파라미터 타입을 강제할 수 있고, as const로 리터럴 타입을 유지했다.
결과
- API 경로 오타로 인한 런타임 에러 제거
- 파라미터 타입 자동 검증
- IDE 자동완성 지원
TypeScript 4.7의 template literal type 개선으로 이전보다 훨씬 정교한 타입 시스템을 구축할 수 있게 되었다.