프론트엔드·백엔드 개발에서 API 응답 처리는 가장 빈번하면서도 가장 많은 버그가 발생하는 지점 중 하나입니다. 특히 TypeScript를 사용하면서도 API 응답을 단순히 any나 무분별한 타입 단언(as)으로 처리하면, TypeScript의 장점을 절반도 활용하지 못하는 상태가 됩니다.
API 응답에서 가장 흔한 문제
다음과 같은 코드는 매우 흔하지만 위험합니다.
const res = await fetch("/api/user");
const data = await res.json();
console.log(data.user.name);
- 응답 구조가 바뀌어도 컴파일 에러 없음
- 필드 누락 시 런타임 오류 발생
- 실제 데이터와 타입 간 불일치 감지 불가
이 문제의 핵심 원인은 단 하나입니다. API 응답이 타입으로 모델링되지 않았기 때문입니다.
API 응답은 반드시 타입으로 정의해야 한다
가장 기본적인 원칙은 “API 응답 하나당 타입 하나”입니다.
type User = {
id: number;
name: string;
};
type UserResponse = {
user: User;
};
이제 응답 데이터는 다음과 같이 다룰 수 있습니다.
const data: UserResponse = await res.json();
console.log(data.user.name);
하지만 이 방식에는 아직 중요한 문제가 남아 있습니다.
성공/실패를 고려하지 않은 응답 모델의 한계
실제 API 응답은 대부분 다음 두 가지 케이스를 가집니다.
- 성공 응답
- 실패(에러) 응답
이를 하나의 타입으로 표현하면 안전하지 않습니다.
type ApiResponse = {
data?: User;
error?: string;
};
이 경우 매번 if 체크가 필요하고, 타입 안정성도 떨어집니다.
Discriminated Union으로 응답 모델링하기
실무에서 가장 권장되는 패턴은 Discriminated Union 기반의 API 응답 타입입니다.
type ApiSuccess<T> = {
ok: true;
data: T;
};
type ApiFailure = {
ok: false;
errorCode: string;
message: string;
};
type ApiResponse<T> = ApiSuccess<T> | ApiFailure;
이제 응답은 반드시 성공 또는 실패 중 하나입니다.
타입 안전한 응답 처리
const response: ApiResponse<User> = await res.json();
if (response.ok) {
response.data.name; // User 타입으로 안전
} else {
console.error(response.message); // 실패 타입
}
이 구조의 핵심 장점은 다음과 같습니다.
- ok 값 하나로 타입이 자동 분기됨
- 잘못된 필드 접근이 컴파일 단계에서 차단
- 에러 케이스 누락 방지
fetch 헬퍼 함수로 공통화하기
실무에서는 API 호출을 공통 함수로 감싸는 경우가 많습니다.
async function fetchJson<T>(
input: RequestInfo,
init?: RequestInit
): Promise<ApiResponse<T>> {
const res = await fetch(input, init);
return res.json();
}
const userRes = await fetchJson<User>("/api/user");
if (userRes.ok) {
userRes.data.id;
}
이 패턴은 다음과 같은 장점을 가집니다.
- API 응답 처리 방식 통일
- 중복 코드 제거
- 타입 안전성 전파
JSON 파싱 시 unknown부터 시작하기
API 응답은 외부 입력이므로 엄밀하게 말하면 unknown부터 시작하는 것이 가장 안전합니다.
const raw: unknown = await res.json();
그리고 런타임 검증을 통해 타입으로 승격시킵니다.
대규모 서비스나 외부 API 연동 시에는 이 방식이 특히 중요합니다.
실무에서 자주 쓰는 응답 패턴 정리
- 모든 API 응답은 제네릭 ApiResponse<T> 사용
- 성공/실패는 Discriminated Union으로 분리
- optional 필드로 성공/실패 표현하지 않기
- 공통 fetch 헬퍼로 타입 전파
- 외부 API는 unknown + 검증 패턴 고려
이 원칙을 지키면 API 관련 버그의 상당수를 컴파일 단계에서 제거할 수 있습니다.
프론트엔드·백엔드 공통 타입 전략
가능하다면 API 응답 타입은 프론트엔드와 백엔드가 공유하는 것이 가장 이상적입니다.
- OpenAPI 기반 타입 생성
- 공통 패키지로 타입 분리
- DTO와 API Response 타입 분리
이렇게 하면 API 변경 시 컴파일 에러로 즉시 감지할 수 있습니다.
타입 안전한 API 응답 처리는 “응답을 신뢰하지 않고, 타입으로 통제하는 설계”입니다. Discriminated Union과 제네릭을 활용하면 API 응답은 더 이상 불안정한 JSON이 아니라 신뢰할 수 있는 계약(Contract)이 됩니다.
- API 응답은 반드시 타입으로 모델링
- 성공/실패는 명확히 분리
- 타입은 공통화, 처리는 일관되게
'개발 > Typescript' 카테고리의 다른 글
| [TYPESCRIPT] TypeScript + Express 프로젝트 설정하기 (0) | 2025.12.27 |
|---|---|
| [TYPESCRIPT] Axios와 함께 쓰는 TypeScript — API 호출을 타입으로 통제하는 실무 패턴 (0) | 2025.12.26 |
| [TYPESCRIPT] TypeScript에서 JSON 다루기 - 타입 안정성으로 런타임 오류 줄이기 (0) | 2025.12.24 |
| [TYPESCRIPT] DOM 타입 정의 이해하기 — TypeScript로 안전한 브라우저 코드 작성 (0) | 2025.12.23 |
| [TYPESCRIPT] 함수형 프로그래밍과 TypeScript - 불변성과 타입으로 만드는 안정적인 코드 (0) | 2025.12.22 |
