10월 1일
오늘 배운 것 (TIL)
Zod를 활용한 TypeScript 타입 안전성과 런타임 유효성 검증
핵심 요약 (TL;DR)
Zod는 TypeScript-first 스키마 선언 및 유효성 검증 라이브러리로, 정적 타입과 런타임 검증을 동시에 제공하여 외부 데이터의 안전성을 보장한다.
Zod란?
TypeScript-first 스키마 선언 및 유효성 검증 라이브러리
- 정적 타입 추론과 런타임 유효성 검증을 동시에 제공
- API 응답, 사용자 입력 등 외부 데이터 검증에 최적화
- Zero dependencies (의존성 없음)
- 직관적인 체이닝 API
기본 사용법
1. 설치
npm install zod
# or
pnpm add zod2. 기본 스키마 정의
import { z } from "zod";
// 기본 타입 스키마
const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();
const dateSchema = z.date();
// 객체 스키마
const userSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
age: z.number().min(0).max(120),
});
// 배열 스키마
const numbersSchema = z.array(z.number());
const usersSchema = z.array(userSchema);3. 유효성 검증
// parse: 검증 실패 시 에러 던짐
try {
const user = userSchema.parse({
id: 1,
name: "John",
email: "john@example.com",
age: 25,
});
console.log(user); // 타입 안전한 데이터
} catch (error) {
console.error(error); // ZodError
}
// safeParse: 검증 결과를 객체로 반환
const result = userSchema.safeParse({
id: 1,
name: "John",
email: "invalid-email",
age: 25,
});
if (result.success) {
console.log(result.data); // 타입 안전한 데이터
} else {
console.log(result.error); // ZodError
}주요 기능
1. 타입 추론
const userSchema = z.object({
id: z.number(),
name: z.string(),
});
// TypeScript 타입 자동 추론
type User = z.infer<typeof userSchema>;
// type User = { id: number; name: string; }2. 선택적 필드와 기본값
const configSchema = z.object({
host: z.string(),
port: z.number().default(3000),
debug: z.boolean().optional(),
timeout: z.number().nullable(), // null 허용
});
type Config = z.infer<typeof configSchema>;
// { host: string; port: number; debug?: boolean; timeout: number | null; }3. 문자열 유효성 검증
const stringValidation = z.object({
email: z.string().email(),
url: z.string().url(),
uuid: z.string().uuid(),
minLength: z.string().min(5),
maxLength: z.string().max(20),
pattern: z.string().regex(/^[A-Z]+$/),
trim: z.string().trim(), // 공백 제거
});4. 숫자 유효성 검증
const numberValidation = z.object({
positive: z.number().positive(),
negative: z.number().negative(),
int: z.number().int(),
range: z.number().min(0).max(100),
multipleOf: z.number().multipleOf(5),
});5. 객체 변환 (transform)
const dateSchema = z.string().transform((str) => new Date(str));
const result = dateSchema.parse("2025-10-01");
console.log(result); // Date 객체6. 조건부 검증 (refine)
const passwordSchema = z
.string()
.refine((password) => password.length >= 8 && /[A-Z]/.test(password), {
message: "비밀번호는 8자 이상이며 대문자를 포함해야 합니다",
});7. Union과 Discriminated Union
// 기본 Union
const stringOrNumber = z.union([z.string(), z.number()]);
// Discriminated Union (태그 기반)
const animalSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal("dog"), bark: z.boolean() }),
z.object({ type: z.literal("cat"), meow: z.boolean() }),
]);
type Animal = z.infer<typeof animalSchema>;
// { type: "dog"; bark: boolean } | { type: "cat"; meow: boolean }실전 활용 사례
1. API 응답 검증
const apiResponseSchema = z.object({
data: z.array(
z.object({
id: z.number(),
title: z.string(),
createdAt: z.string().datetime(),
}),
),
meta: z.object({
page: z.number(),
totalPages: z.number(),
}),
});
async function fetchData() {
const response = await fetch("/api/data");
const json = await response.json();
// 런타임 검증
const validated = apiResponseSchema.parse(json);
return validated; // 타입 안전 보장
}2. Form 데이터 검증 (React Hook Form 통합)
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
const loginSchema = z.object({
email: z.string().email("유효한 이메일을 입력하세요"),
password: z.string().min(8, "비밀번호는 최소 8자 이상이어야 합니다"),
});
type LoginForm = z.infer<typeof loginSchema>;
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm<LoginForm>({
resolver: zodResolver(loginSchema),
});
const onSubmit = (data: LoginForm) => {
// data는 이미 검증된 상태
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} />
{errors.email && <span>{errors.email.message}</span>}
<input type="password" {...register("password")} />
{errors.password && <span>{errors.password.message}</span>}
<button type="submit">로그인</button>
</form>
);
}3. 환경 변수 검증
const envSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(1),
PORT: z.string().transform(Number).pipe(z.number().min(1).max(65535)),
});
// 앱 시작 시 환경 변수 검증
export const env = envSchema.parse(process.env);Zod vs 다른 검증 라이브러리
| 특징 | Zod | Yup | Joi |
|---|---|---|---|
| TypeScript 우선 | ✅ | ⚠️ (지원) | ❌ |
| 타입 추론 | ✅ | ⚠️ | ❌ |
| Zero dependencies | ✅ | ❌ | ❌ |
| 번들 크기 | 작음 (12kb) | 중간 | 큼 |
| 체이닝 API | ✅ | ✅ | ❌ |
| 런타임 검증 | ✅ | ✅ | ✅ |
참고 자료
Edit on GitHub
Last updated on