TypeScript 4.4 템플릿 리터럴 타입으로 API 경로 타입 안전하게 관리하기

문제 상황

프로젝트에서 RESTful API 호출 시 경로를 문자열로 직접 작성하고 있었다. 리팩토링 과정에서 /api/users/:id 경로를 /api/user/:id로 변경했는데, 클라이언트 코드 일부를 수정하지 못해 404 에러가 발생했다.

// 기존 코드
const user = await fetch(`/api/users/${userId}`);
const posts = await fetch(`/api/user/${userId}/posts`); // 오타

해결 방법

TypeScript 4.4에서 강화된 템플릿 리터럴 타입을 활용해 API 경로를 타입으로 정의했다.

type UserId = string;
type PostId = string;

type ApiRoute = 
  | `/api/users`
  | `/api/users/${UserId}`
  | `/api/users/${UserId}/posts`
  | `/api/posts/${PostId}`;

function apiCall<T>(route: ApiRoute): Promise<T> {
  return fetch(route).then(res => res.json());
}

// 이제 타입 체크로 오타를 방지할 수 있다
apiCall(`/api/users/${userId}`); // OK
apiCall(`/api/user/${userId}`); // 컴파일 에러

더 나아가 경로 파라미터를 추출하는 유틸리티 타입도 만들었다.

type ExtractParams<T extends string> = 
  T extends `${infer _Start}/:${infer Param}/${infer Rest}`
    ? Param | ExtractParams<`/${Rest}`>
    : T extends `${infer _Start}/:${infer Param}`
    ? Param
    : never;

type UserRouteParams = ExtractParams<'/api/users/:userId/posts/:postId'>;
// 결과: 'userId' | 'postId'

결과

  • API 경로 오타로 인한 런타임 에러 사전 방지
  • IDE 자동완성으로 개발 생산성 향상
  • 리팩토링 시 타입 체커가 수정 누락 지점을 알려줌

템플릿 리터럴 타입은 생각보다 강력했다. 다음에는 이를 활용해 라우터 타입을 더 개선해볼 계획이다.

TypeScript 4.4 템플릿 리터럴 타입으로 API 경로 타입 안전하게 관리하기