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의 개선된 타입 추론 덕분에 이전보다 안정적으로 동작한다.

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