TypeScript 4.5 템플릿 리터럴 타입으로 API 라우트 타입 안전하게 관리하기

문제 상황

프로젝트의 API 엔드포인트가 늘어나면서 URL 오타로 인한 404 에러가 자주 발생했다. 특히 동적 라우트(/users/:id)를 사용할 때 문자열 보간 실수가 많았다.

// 기존 방식 - 런타임에야 오류 발견
const userId = 123;
fetch(`/api/user/${userId}`); // 실제론 /api/users/:id

TypeScript 4.5 템플릿 리터럴 활용

TypeScript 4.5의 개선된 템플릿 리터럴 타입을 활용해 API 라우트를 타입 안전하게 만들었다.

type ApiRoute = 
  | '/api/users'
  | `/api/users/${number}`
  | '/api/posts'
  | `/api/posts/${number}`
  | `/api/posts/${number}/comments`;

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

// 타입 체크 통과
apiCall('/api/users/123');

// 컴파일 에러 발생
apiCall('/api/user/123'); // Argument of type ... is not assignable

제네릭으로 확장

더 나아가 리소스 타입과 ID를 제네릭으로 받아 재사용성을 높였다.

type Resource = 'users' | 'posts' | 'comments';
type ResourceRoute<T extends Resource> = 
  | `/api/${T}`
  | `/api/${T}/${number}`;

function getResource<T extends Resource>(
  resource: T,
  id?: number
): ResourceRoute<T> {
  return id 
    ? `/api/${resource}/${id}` as ResourceRoute<T>
    : `/api/${resource}` as ResourceRoute<T>;
}

결과

  • API 경로 오타로 인한 런타임 에러 90% 감소
  • IDE 자동완성으로 개발 속도 향상
  • 리팩토링 시 타입 체커가 영향받는 코드 자동 탐지

템플릿 리터럴 타입은 문자열 기반 API를 다룰 때 특히 유용했다. 다음엔 query string까지 타입으로 관리하는 방법을 시도해볼 예정이다.

TypeScript 4.5 템플릿 리터럴 타입으로 API 라우트 타입 안전하게 관리하기