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

문제 상황

프로젝트에서 REST API 엔드포인트를 문자열로 관리하다 보니 오타로 인한 런타임 에러가 종종 발생했다. /api/users/:id처럼 동적 경로를 포함한 엔드포인트는 타입 체크가 전혀 되지 않았다.

// 기존 방식
const endpoint = `/api/users/${userId}/posts/${postId}`;
// 오타가 있어도 컴파일 타임에 잡히지 않음

Template Literal Types 적용

TypeScript 4.1에서 추가된 Template Literal Types를 사용해 문자열 조합을 타입 레벨에서 처리할 수 있게 되었다.

type ApiResource = 'users' | 'posts' | 'comments';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiEndpoint = `/api/${ApiResource}` | `/api/${ApiResource}/${string}`;

const getEndpoint = (resource: ApiResource, id?: string): ApiEndpoint => {
  return id ? `/api/${resource}/${id}` : `/api/${resource}`;
};

// 타입 체크 통과
const valid = getEndpoint('users', '123');
// 타입 에러 발생
const invalid = getEndpoint('articles', '123');

CSS 클래스명 관리에도 활용

Tailwind CSS나 CSS Modules 없이 직접 클래스명을 관리할 때도 유용했다.

type Variant = 'primary' | 'secondary' | 'danger';
type Size = 'sm' | 'md' | 'lg';
type ButtonClass = `btn-${Variant}` | `btn-${Size}`;

const getButtonClass = (variant: Variant, size: Size): string => {
  const classes: ButtonClass[] = [`btn-${variant}`, `btn-${size}`];
  return classes.join(' ');
};

한계점

완벽하지는 않다. 유니온이 너무 커지면 컴파일러가 타입을 string으로 fallback하고, IDE 자동완성도 느려진다. 적절한 선에서 사용해야 한다.

그래도 문자열 기반 API를 다룰 때 타입 안전성을 크게 개선할 수 있어 만족스러웠다.

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