TypeScript 4.3 template literal types로 API 경로 타입 안전하게 관리하기

문제 상황

프로젝트에서 REST API 경로를 문자열로 관리하다 보니 오타로 인한 404 에러가 자주 발생했다. /api/users/:id 같은 경로를 하드코딩하면 리팩토링 시 누락되는 경우도 많았다.

// 기존 방식
const getUserPath = (id: string) => `/api/users/${id}`;
const getPostPath = (userId: string, postId: string) => 
  `/api/users/${userId}/posts/${postId}`;

Template Literal Types 적용

TypeScript 4.1부터 지원하는 template literal types를 활용해 경로를 타입으로 정의했다.

type ApiPath = 
  | '/api/users'
  | '/api/posts'
  | `/api/users/${string}`
  | `/api/users/${string}/posts/${string}`;

function fetchApi(path: ApiPath) {
  return fetch(path);
}

// OK
fetchApi('/api/users');
fetchApi('/api/users/123');

// Error: Argument of type '/api/user' is not assignable
fetchApi('/api/user'); // 오타 감지

동적 경로 생성 헬퍼

타입 안전성을 유지하면서 동적으로 경로를 생성하는 헬퍼 함수도 만들었다.

type Route = {
  users: `/api/users/${string}`;
  userPosts: `/api/users/${string}/posts/${string}`;
};

const route = {
  users: (id: string): Route['users'] => `/api/users/${id}`,
  userPosts: (userId: string, postId: string): Route['userPosts'] => 
    `/api/users/${userId}/posts/${postId}`,
};

// 타입 체크와 자동완성 지원
const path = route.users('123');

결과

도입 후 API 경로 관련 버그가 거의 사라졌다. IDE의 자동완성도 제대로 작동해서 생산성도 올랐다. 다만 경로가 많아지면 타입 정의가 길어지는 단점은 있지만, 런타임 안정성을 생각하면 충분히 감수할 만했다.