TypeScript Zod 검증 패턴

Zod 스키마를 사용하여 런타임 데이터 검증 및 정적 타입을 동시에 관리하는 패턴 모음입니다.

Gist
import { z } from 'zod';

/**
 * 사용자 정보 스키마 정의
 */
const userSchema = z.object({
  id: z.string().uuid(),
  username: z.string().min(3).max(20),
  email: z.string().email(),
  age: z.number().int().min(18).optional(),
  role: z.enum(['admin', 'user', 'guest']).default('user'),
  createdAt: z.date().default(() => new Date()),
});

/**
 * 스키마로부터 타입 추출
 */
type User = z.infer<typeof userSchema>;

/**
 * 입력 데이터 검증 예시
 */
function validateUser(data: unknown) {
  const result = userSchema.safeParse(data);
  
  if (!result.success) {
    // 검증 실패 시 상세 에러 메시지 구성
    const errorMessages = result.error.errors.map(err => ({
      path: err.path.join('.'),
      message: err.message
    }));
    return { success: false, errors: errorMessages };
  }

  // 검증 성공 시 타입이 보장된 데이터 반환
  return { success: true, data: result.data };
}

/**
 * 복잡한 조건부 검증 (Refine) 예시
 */
const passwordResetSchema = z.object({
  password: z.string().min(8),
  confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
  message: "Passwords don't match",
  path: ["confirmPassword"],
});

/**
 * API 요청 본문 검증 미들웨어 예시 (가상)
 */
const createUpdateUserSchema = userSchema.partial().extend({
  username: z.string().min(3).max(20),
});

// 사용 예시
const rawData = {
  id: "550e8400-e29b-41d4-a716-446655440000",
  username: "johndoe",
  email: "invalid-email",
  age: 15
};

const validation = validateUser(rawData);
if (!validation.success) {
  console.log("Validation failed:", validation.errors);
}