TypeScript 4.4 template literal type으로 타입 안전성 높이기

문제 상황

프로젝트에서 RESTful API 엔드포인트를 string으로 관리하다 보니 오타로 인한 404 에러가 종종 발생했다. /api/users/:id 같은 동적 경로는 더 문제였다.

// 기존 방식
const endpoint = `/api/users/${userId}`; // 오타 가능성
fetch(endpoint);

Template Literal Types 활용

TypeScript 4.1부터 지원된 template literal type을 4.4에서 더 강화된 방식으로 활용했다.

type ApiVersion = 'v1' | 'v2';
type Resource = 'users' | 'posts' | 'comments';
type ApiPath = `/api/${ApiVersion}/${Resource}`;

// 자동완성 지원되고 타입 체크됨
const path: ApiPath = '/api/v1/users'; // OK
const invalid: ApiPath = '/api/v3/users'; // Error

동적 ID가 필요한 경우도 처리 가능하다.

type IdPath<T extends string> = `${T}/${number}`;
type UserIdPath = IdPath<'/api/v1/users'>;

// 타입 가드와 함께 사용
function fetchUser(path: UserIdPath) {
  return fetch(path);
}

실용적인 API 클라이언트

실제 프로젝트에 적용한 래퍼 함수다.

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = `/api/${string}`;

class ApiClient {
  async request<T>(
    method: HttpMethod,
    endpoint: Endpoint,
    data?: unknown
  ): Promise<T> {
    const response = await fetch(endpoint, {
      method,
      headers: { 'Content-Type': 'application/json' },
      body: data ? JSON.stringify(data) : undefined,
    });
    return response.json();
  }
}

const api = new ApiClient();
api.request('GET', '/api/users/123'); // OK
api.request('GET', '/wrong/path'); // Error

결과

  • IDE에서 자동완성으로 엔드포인트 입력 편의성 증가
  • 컴파일 타임에 잘못된 경로 차단
  • 리팩토링 시 타입 에러로 누락 방지

아직 복잡한 쿼리 파라미터까지는 타이핑하지 않았지만, 기본적인 경로 검증만으로도 충분히 효과를 봤다. TypeScript 4.4의 개선된 타입 추론 덕분에 이전보다 안정적으로 동작한다.