TypeScript 5.x 조건부 타입으로 API 응답 타입 추론 개선하기
문제 상황
프로젝트에서 REST API 클라이언트를 사용하는데, 엔드포인트마다 응답 타입을 일일이 제네릭으로 전달해야 했다.
const user = await api.get<UserResponse>('/users/123');
const posts = await api.get<PostListResponse>('/posts');
엔드포인트가 100개가 넘다 보니 타입을 잘못 지정하거나 누락하는 경우가 종종 발생했다.
해결 방법
조건부 타입과 템플릿 리터럴 타입을 조합해서 엔드포인트 경로로부터 응답 타입을 자동 추론하도록 개선했다.
type ApiEndpoints = {
'/users/:id': UserResponse;
'/posts': PostListResponse;
'/posts/:id': PostDetailResponse;
};
type ExtractParams<T extends string> =
T extends `${infer _Start}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractParams<Rest>]: string }
: T extends `${infer _Start}:${infer Param}`
? { [K in Param]: string }
: {};
type ApiClient = {
get<T extends keyof ApiEndpoints>(
endpoint: T,
params?: ExtractParams<T>
): Promise<ApiEndpoints[T]>;
};
이제 엔드포인트 경로만 입력하면 타입이 자동으로 추론된다.
const user = await api.get('/users/:id', { id: '123' }); // UserResponse
const posts = await api.get('/posts'); // PostListResponse
결과
- 타입 캐스팅 에러가 80% 이상 감소했다
- 새로운 엔드포인트 추가 시 ApiEndpoints만 확장하면 됨
- IDE 자동완성으로 개발 속도도 개선되었다
다만 동적 라우팅이 복잡한 경우 타입 추론이 제대로 안 되는 경우가 있어서, 일부는 여전히 수동 타입 지정을 병행하고 있다.