런타임 API 응답 검증: TypeScript만으로는 부족한 이유
TypeScript은 컴파일 타임에만 타입을 검사합니다. API에서 오는 데이터는 런타임에 도착하며, 인터페이스와 일치한다는 보장이 없습니다. 추가 검증 없이 불일치가 발생하면 사용자에게 노출되는 오류로 나타납니다: undefined 필드, 잘못된 타입, 누락된 객체 등.
런타임 검사 없이 발생하는 문제
TypeScript 인터페이스는 거짓된 안정감을 줍니다. 검증 없는 전형적인 코드 예시:
interface User {
id: number;
name: string;
email: string;
}
const getUser = async (): Promise<User> => {
const res = await fetch('/api/user');
return res.json(); // 실제 데이터 검사 없음
};
TypeScript은 타입 주석을 신뢰하고 실제 서버 응답을 무시합니다. 주요 위험:
- 백엔드 구조 변경:
full_name→fullName으로 바뀌면undefined발생. - 타입 불일치: 숫자
123대신 문자열"123"이 오면 덧셈 대신 연결. - 조건부 필드: 특수 케이스에서
address누락,user.address.city에서 크래시. - 문서가 오래된 서드파티 API.
스키마 검증 라이브러리
해결책? 런타임 스키마 검증 라이브러리입니다. 실제 데이터를 파싱하고 불일치 시 오류를 발생시킵니다. TypeScript 최고 옵션:
- Zod: 스키마에서 타입 추론, 방대한 생태계.
- Yup: Formik과 완벽 호환.
- Valibot: 번들 친화적, 트리셰이킹 가능.
- Joi: Node.js 기반, 백엔드 중심.
- ArkType: 성능 최우선.
Zod로 구현하기
Zod는 스키마, 타입, 검증을 하나로 통합합니다. 사용자 예시:
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>;
const getUser = async (): Promise<User> => {
const res = await fetch('/api/user');
const data = await res.json();
return UserSchema.parse(data); // 불일치 시 오류 발생
};
parse()는 실패 시 상세 정보를 담은 ZodError를 던집니다. success와 error를 가진 객체로 반환하는 safeParse()를 사용하세요.
실제 프로젝트에서 확장하기
대형 앱에서는 모든 엔드포인트의 스키마를 중앙화하세요. fetch를 이렇게 래핑:
const apiFetch = async <T>(url: string, schema: z.ZodSchema<T>): Promise<T> => {
const res = await fetch(url);
const data = await res.json();
return schema.parse(data);
};
const users = await apiFetch('/api/users', z.array(UserSchema));
오류 처리: Sentry에 로깅, 사용자 친화적 메시지 표시. 배열의 경우 각 항목 검증.
- 리스트에는
z.array(UserSchema)사용. - 다형 응답에는
z.union(). - 재귀 구조에는
z.lazy(). - 데이터 정규화에는
z.transform().
주요 요점
- TypeScript은 외부 데이터(API, localStorage, URL 파라미터)에 대한 런타임 타입을 보장하지 않습니다.
- 스키마 검증으로 불일치를 조기 발견, 디버깅 시간 단축.
- Zod는 프론트엔드 표준: 스키마에서 타입 생성, 쉬운 통합.
- 별도 백엔드를 둔 팀에서 조용한 실패 방지.
- 일관성을 위해 fetch 래퍼로 확장.
런타임 검증은 TypeScript의 컴파일 타임 검사와 실행 사이 공백을 메웁니다. 프로덕션에서 API 계약 불일치로 인한 버그를 대폭 줄입니다.
— Editorial Team
아직 댓글이 없습니다.