TypeScript 4.6 템플릿 리터럴 타입으로 API 경로 타입 안전하게 관리하기
문제 상황
프로젝트에서 REST API 경로를 문자열로 하드코딩하다 보니 /api/users/:id 같은 동적 경로에서 오타가 자주 발생했다. 특히 리팩토링 시 경로가 변경되면 모든 호출부를 찾아 수정해야 했다.
// 기존 방식
const getUserUrl = (id: string) => `/api/users/${id}`;
const getPostUrl = (userId: string, postId: string) =>
`/api/users/${userId}/posts/${postId}`;
템플릿 리터럴 타입 활용
TypeScript 4.1부터 지원된 템플릿 리터럴 타입을 활용해 타입 안전한 경로 빌더를 만들었다.
type ApiRoute = '/api/users' | '/api/posts';
type WithId<T extends string> = `${T}/${string}`;
type UserRoutes = '/api/users' | WithId<'/api/users'>;
type PostRoutes = WithId<'/api/users'> & `${string}/posts/${string}`;
function buildRoute<T extends string>(
base: T,
...params: string[]
): `${T}/${string}` {
return `${base}/${params.join('/')}` as `${T}/${string}`;
}
// 사용
const userUrl = buildRoute('/api/users', userId); // 타입 체크 통과
const wrongUrl = buildRoute('/api/wrong', id); // 컴파일 에러
실전 적용
API 클라이언트에 적용하니 잘못된 경로 사용이 IDE에서 즉시 감지됐다. 경로 변경 시에도 타입 에러로 모든 사용처를 찾을 수 있어 리팩토링이 안전해졌다.
class ApiClient {
get<T>(route: UserRoutes | PostRoutes): Promise<T> {
return fetch(route).then(r => r.json());
}
}
const api = new ApiClient();
await api.get(`/api/users/${id}`); // OK
await api.get('/api/invalid'); // 컴파일 에러
타입 시스템으로 해결할 수 있는 영역이 계속 넓어지고 있다.