Frontend/Typescript
Zod를 활용한 런타임 타입 검증: 실행 시점의 데이터 무결성 확보하기
미니임
2026. 2. 20. 23:30

TypeScript는 강력한 타입 시스템을 제공하지만, 이는 컴파일 타임에만 유효합니다. 실제 애플리케이션이 실행되는 런타임(Runtime) 환경에서 외부 API 호출 결과나 사용자 입력값은 언제든 우리가 정의한 타입과 다를 수 있습니다.
Zod는 이러한 간극을 메워주는 스키마 선언 및 유효성 검사 라이브러리입니다. Zod를 사용하면 데이터의 구조를 정의함과 동시에 실행 시점에 해당 데이터가 유효한지 검증할 수 있습니다.
1. 왜 Zod인가? (TypeScript의 한계)
TypeScript는 "이 변수는 이 타입일 것이다"라고 가정하고 코드를 짭니다. 하지만 외부에서 들어오는 데이터는 통제할 수 없습니다.
interface User {
name: string;
age: number;
}
// 만약 API 응답으로 { name: "John", age: "30" } (문자열 나이)이 온다면?
// TS는 에러를 내지 않지만, 실행 중에 age.toFixed() 등을 호출하면 런타임 에러가 발생합니다.
const userData: User = await fetch('/api/user').then(res => res.json());
Zod는 데이터를 "믿는" 대신 "검증"합니다.
2. Zod 기초: 스키마 정의 및 검증
Zod를 사용하면 먼저 스키마를 정의하고, 해당 스키마를 통해 데이터를 파싱(Parsing)합니다.
2.1. 기본 타입 정의
import { z } from 'zod';
const UserSchema = z.object({
id: z.string().uuid(), // UUID 형식의 문자열
name: z.string().min(2).max(20), // 2~20자 사이의 문자열
email: z.string().email(), // 이메일 형식
age: z.number().int().positive(), // 양의 정수
isPremium: z.boolean().default(false), // 기본값 설정
});
// 데이터 검증
const result = UserSchema.safeParse({
id: "550e8400-e29b-41d4-a716-446655440000",
name: "Kim",
email: "test@example.com",
age: 25,
});
if (result.success) {
console.log("검증 성공:", result.data);
} else {
console.error("검증 실패:", result.error.format());
}
3. infer를 이용한 TypeScript와의 연결
Zod의 가장 큰 장점 중 하나는 정의한 스키마로부터 TypeScript 타입을 자동으로 추출할 수 있다는 점입니다. 이를 통해 중복 선언을 방지할 수 있습니다.
// 스키마로부터 타입 추출
type UserType = z.infer<typeof UserSchema>;
// 이제 UserType은 아래와 동일하게 동작합니다.
/*
type UserType = {
id: string;
name: string;
email: string;
age: number;
isPremium: boolean;
}
*/
4. 고급 검증 기법
단순한 타입 체크를 넘어 비즈니스 로직을 검증에 포함할 수 있습니다.
4.1. refine을 이용한 커스텀 검증
const PasswordSchema = z.string()
.min(8)
.refine((val) => /[0-9]/.test(val), {
message: "비밀번호는 숫자를 포함해야 합니다.",
});
const RegistrationSchema = z.object({
password: z.string(),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "비밀번호가 일치하지 않습니다.",
path: ["confirmPassword"], // 에러가 발생한 필드 지정
});
4.2. transform을 이용한 데이터 변환
데이터를 검증함과 동시에 원하는 형태로 가공할 수 있습니다.
const SearchSchema = z.object({
query: z.string().trim().toLowerCase(), // 공백 제거 및 소문자 변환
page: z.string().transform(Number), // 문자열을 숫자로 변환
});
5. 실무 활용: Next.js API Routes 적용
API로 들어오는 데이터(request body)를 Zod로 가드(Guard)하는 패턴입니다.
import type { NextApiRequest, NextApiResponse } from 'next';
import { z } from 'zod';
const ContactSchema = z.object({
email: z.string().email(),
message: z.string().min(10, "메시지는 최소 10자 이상이어야 합니다."),
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') return res.status(405).end();
const validation = ContactSchema.safeParse(req.body);
if (!validation.success) {
// 유효성 검사 실패 시 구체적인 에러 메시지 반환
return res.status(400).json({
error: "유효하지 않은 요청입니다.",
details: validation.error.flatten().fieldErrors
});
}
// validation.data는 이제 완벽하게 타입이 추론된 안전한 데이터입니다.
const { email, message } = validation.data;
// DB 저장 로직...
res.status(200).json({ success: true });
}
요약: Zod 도입의 이점
- 타입 안전성(Type Safety): TypeScript의 정적 타입과 런타임의 실제 데이터를 완벽하게 일치시킵니다.
- 코드 중복 제거: 스키마 하나로 검증 로직과 TS 타입을 동시에 관리합니다(z.infer).
- 가독성 높은 에러: 사용자에게 보여주기 적합한 상세한 에러 정보를 제공합니다.
- 선언적 코드: 복잡한 if 조건문 도배 없이 선언적으로 데이터 구조를 정의합니다.
Zod는 더 이상 선택이 아닌, 견고한 TypeScript 애플리케이션을 위한 필수 도구입니다.
반응형