TypeScript 4.1의 Template Literal Types로 타입 안정성 높이기

배경

프로젝트에서 REST API 엔드포인트를 문자열로 관리하다 보니 오타로 인한 런타임 에러가 종종 발생했다. /api/users/detail/api/user/detail로 잘못 입력하는 식의 실수였다.

TypeScript 4.1이 11월에 릴리즈되면서 Template Literal Types가 추가됐다는 소식을 듣고 바로 적용해봤다.

Template Literal Types 적용

기존에는 문자열 리터럴 유니온으로만 관리했다.

type Endpoint = '/api/users' | '/api/posts' | '/api/comments';

이제는 패턴을 조합할 수 있다.

type Resource = 'users' | 'posts' | 'comments';
type Action = 'list' | 'detail' | 'create';

type Endpoint = `/api/${Resource}/${Action}`;
// '/api/users/list' | '/api/users/detail' | ... (9개 조합)

실제 적용 사례

API 클라이언트에 적용했다.

type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';

interface ApiConfig {
  baseURL: string;
}

class ApiClient {
  constructor(private config: ApiConfig) {}

  request<T>(method: HTTPMethod, endpoint: Endpoint): Promise<T> {
    return fetch(`${this.config.baseURL}${endpoint}`, {
      method,
    }).then(res => res.json());
  }
}

// 사용
const client = new ApiClient({ baseURL: 'https://api.example.com' });
client.request('GET', '/api/users/list'); // ✓
client.request('GET', '/api/user/list');  // ✗ 컴파일 에러

이벤트 시스템에도 활용

이벤트 버스에도 적용해서 이벤트 타입 안정성을 확보했다.

type EventType = 'user' | 'post';
type EventAction = 'created' | 'updated' | 'deleted';
type Event = `${EventType}:${EventAction}`;

class EventBus {
  on(event: Event, handler: Function) {
    // ...
  }
}

const bus = new EventBus();
bus.on('user:created', () => {}); // ✓
bus.on('user:create', () => {});  // ✗ 컴파일 에러

한계

타입 조합이 많아지면 컴파일 시간이 길어진다. Resource 5개 × Action 5개면 25개 조합인데, 여기에 버전까지 추가하면 금방 늘어난다. 적절한 선에서 사용해야 한다.

정리

Template Literal Types 덕분에 문자열 기반 API를 타입 안전하게 관리할 수 있게 됐다. 기존 코드베이스에 점진적으로 적용 중이며, 특히 API 클라이언트와 이벤트 시스템에서 효과가 좋았다.

TypeScript 4.1의 Template Literal Types로 타입 안정성 높이기