TypeScript 4.5 Template Literal Types로 API 경로 타입 안전하게 관리하기

문제 상황

백오피스 프로젝트에서 REST API 경로를 문자열로 관리하다가 /api/users/:id 같은 경로에서 오타가 발생해 런타임 에러가 났다. 특히 동적 파라미터가 들어가는 경로에서 실수가 잦았다.

// 기존 방식
const getUserPath = (id: string) => `/api/user/${id}`; // 오타: users가 아닌 user
fetch(getUserPath('123')); // 404 에러

Template Literal Types 적용

TypeScript 4.5부터 Template Literal Types를 활용하면 경로 패턴을 타입으로 강제할 수 있다.

type APIPath = 
  | '/api/users'
  | '/api/users/:id'
  | '/api/posts'
  | '/api/posts/:id/comments';

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

function buildPath<T extends APIPath>(
  path: T,
  params: ExtractParams<T>
): string {
  let result = path as string;
  Object.entries(params).forEach(([key, value]) => {
    result = result.replace(`:${key}`, value);
  });
  return result;
}

// 사용
const path1 = buildPath('/api/users/:id', { id: '123' }); // OK
const path2 = buildPath('/api/users/:id', { userId: '123' }); // 타입 에러

실전 적용

API 클라이언트 레이어에 적용하니 경로 관련 버그가 사전에 차단됐다. 자동완성도 지원돼서 개발 경험이 개선됐다.

class APIClient {
  get<T extends APIPath>(path: T, params: ExtractParams<T>) {
    return fetch(buildPath(path, params));
  }
}

const client = new APIClient();
client.get('/api/users/:id', { id: '123' }); // 타입 안전

아직 복잡한 쿼리 파라미터까지는 커버하지 못하지만, 기본적인 경로 파라미터 관리는 확실히 안전해졌다.

TypeScript 4.5 Template Literal Types로 API 경로 타입 안전하게 관리하기