TypeScript 4.2 Template Literal Types로 API 라우트 타입 안전하게 관리하기

문제 상황

프로젝트의 API 클라이언트 코드에서 경로 오타로 인한 런타임 에러가 자주 발생했다. /api/users/:id 같은 동적 경로를 문자열로 관리하다 보니 타입 체크가 불가능했다.

// 기존 방식
const endpoint = `/api/user/${userId}`; // users가 아닌 user 오타

Template Literal Types 활용

TypeScript 4.2에서 추가된 Template Literal Types를 사용해 경로 타입을 정의했다.

type ApiVersion = 'v1' | 'v2';
type Resource = 'users' | 'posts' | 'comments';
type ApiRoute = `/api/${ApiVersion}/${Resource}`;
type DynamicRoute<T extends string> = `${T}/${string}`;

// 사용
const route: ApiRoute = '/api/v1/users'; // OK
const route2: ApiRoute = '/api/v1/user'; // Error

const userRoute: DynamicRoute<'/api/v1/users'> = '/api/v1/users/123'; // OK

API 클라이언트 적용

타입을 활용해 API 클라이언트를 리팩토링했다.

class ApiClient {
  get<T>(route: ApiRoute | DynamicRoute<ApiRoute>): Promise<T> {
    return fetch(route).then(res => res.json());
  }
}

const client = new ApiClient();
await client.get('/api/v1/users'); // OK
await client.get('/api/v1/user'); // Compile Error

결과

컴파일 타임에 경로 오타를 잡아낼 수 있게 되어 런타임 에러가 크게 줄었다. IDE 자동완성도 지원되어 개발 경험이 개선되었다. 다만 너무 복잡한 타입은 컴파일 속도에 영향을 줄 수 있어 적절한 수준으로 사용하는 것이 중요했다.

TypeScript 4.2 Template Literal Types로 API 라우트 타입 안전하게 관리하기