TypeScript 타입 가드로 API 응답 안전하게 다루기

문제 상황

외부 결제 API를 연동하면서 응답 데이터의 타입 안정성이 보장되지 않는 문제가 있었다. interface로 타입을 정의했지만, 실제 런타임에서는 예상과 다른 데이터가 들어올 수 있었다.

interface PaymentResponse {
  status: 'success' | 'failed';
  amount: number;
  transactionId: string;
}

const response = await fetch('/api/payment');
const data: PaymentResponse = await response.json(); // 타입 단언만으로는 불충분

타입 가드 도입

타입 가드 함수를 만들어 런타임에 실제 데이터 구조를 검증하도록 했다.

function isPaymentResponse(data: any): data is PaymentResponse {
  return (
    typeof data === 'object' &&
    data !== null &&
    (data.status === 'success' || data.status === 'failed') &&
    typeof data.amount === 'number' &&
    typeof data.transactionId === 'string'
  );
}

const response = await fetch('/api/payment');
const data = await response.json();

if (isPaymentResponse(data)) {
  // 이 블록 안에서는 data가 PaymentResponse 타입으로 추론됨
  console.log(data.transactionId);
} else {
  throw new Error('Invalid payment response');
}

결과

타입 가드를 통해 컴파일 타임과 런타임 모두에서 타입 안정성을 확보했다. API 스펙 변경이나 예상치 못한 응답에도 빠르게 대응할 수 있게 되었다.

유틸리티 함수로 분리해 다른 API 응답 처리에도 동일한 패턴을 적용 중이다.