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의 자동완성도 정확해져 개발 경험이 개선되었다. 다만 템플릿 리터럴 타입이 복잡해지면 컴파일 속도가 느려지는 점은 주의가 필요하다.

TypeScript 4.5 템플릿 리터럴 타입으로 API 라우트 검증하기