TypeScript 4.7 템플릿 리터럴 타입 실전 활용

배경

회사 프로젝트에서 RESTful API 클라이언트를 작성하던 중, 경로 파라미터에 대한 타입 체크가 없어 런타임 에러가 자주 발생했다. TypeScript 4.7에서 템플릿 리터럴 타입이 강화되어 이를 활용해 타입 안전한 API 클라이언트를 구현했다.

구현

type ExtractPathParams<T extends string> = 
  T extends `${infer Start}/:${infer Param}/${infer Rest}`
    ? { [K in Param | keyof ExtractPathParams<`/${Rest}`>]: string }
    : T extends `${infer Start}/:${infer Param}`
    ? { [K in Param]: string }
    : {};

type APIRoutes = {
  '/users/:userId': { method: 'GET' };
  '/users/:userId/posts/:postId': { method: 'GET' };
  '/posts': { method: 'POST' };
};

function apiCall<T extends keyof APIRoutes>(
  path: T,
  params: ExtractPathParams<T>
) {
  let url = path as string;
  Object.entries(params).forEach(([key, value]) => {
    url = url.replace(`:${key}`, value);
  });
  return fetch(url);
}

// 타입 체크 동작
apiCall('/users/:userId', { userId: '123' }); // OK
apiCall('/users/:userId/posts/:postId', { userId: '123', postId: '456' }); // OK
apiCall('/users/:userId', { postId: '123' }); // Error

결과

필요한 파라미터가 누락되거나 잘못된 키를 사용하면 컴파일 단계에서 에러가 발생한다. IDE 자동완성도 잘 동작해서 개발 경험이 크게 개선됐다.

다만 재귀 타입의 깊이 제한으로 매우 긴 경로는 처리하지 못하는 한계가 있었다. 실무에서는 3단계 이상의 중첩된 파라미터가 거의 없어 문제되지 않았다.

참고

  • TypeScript 4.7 Release Notes
  • 프로젝트에서 약 50개의 API 엔드포인트에 적용 완료