TypeScript

Zod: TypeScript 스키마 검증

코샵 2025. 2. 20. 10:46
반응형
# 설치
npm install zod

기본 사용법

import { z } from "zod";

// 기본 스키마 정의
const userSchema = z.object({
  name: z.string(),
  age: z.number().min(0),
  email: z.string().email(),
  website: z.string().url().optional()
});

// 타입 추론
type User = z.infer<typeof userSchema>;

// 데이터 검증
try {
  const user = userSchema.parse({
    name: "John",
    age: 30,
    email: "john@example.com"
  });
} catch (error) {
  console.error(error);
}

고급 스키마 정의

const addressSchema = z.object({
  street: z.string(),
  city: z.string(),
  country: z.string()
});

const userProfileSchema = z.object({
  id: z.number(),
  username: z.string().min(3).max(20),
  email: z.string().email(),
  age: z.number().min(18).max(100),
  isActive: z.boolean(),
  address: addressSchema,
  tags: z.array(z.string()).min(1).max(5),
  settings: z.record(z.string(), z.any())
});

조건부 검증

const passwordSchema = z.object({
  password: z.string()
    .min(8, "비밀번호는 최소 8자 이상이어야 합니다")
    .regex(/[A-Z]/, "대문자를 포함해야 합니다")
    .regex(/[0-9]/, "숫자를 포함해야 합니다"),
  confirmPassword: z.string()
}).refine((data) => data.password === data.confirmPassword, {
  message: "비밀번호가 일치하지 않습니다",
  path: ["confirmPassword"]
});

유니온 타입과 열거형

const Role = z.enum(["admin", "user", "guest"]);

const UserStatus = z.union([
  z.literal("active"),
  z.literal("inactive"),
  z.literal("pending")
]);

const userSchema = z.object({
  id: z.number(),
  role: Role,
  status: UserStatus,
  data: z.discriminatedUnion("type", [
    z.object({ type: z.literal("admin"), permissions: z.array(z.string()) }),
    z.object({ type: z.literal("user"), subscription: z.string() })
  ])
});

React와 함께 사용

import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

const formSchema = z.object({
  username: z.string().min(3),
  email: z.string().email(),
  age: z.number().min(18)
});

function Form() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(formSchema)
  });

  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      <input {...register("username")} />
      {errors.username && <span>{errors.username.message}</span>}

      <input {...register("email")} />
      {errors.email && <span>{errors.email.message}</span>}

      <input {...register("age", { valueAsNumber: true })} />
      {errors.age && <span>{errors.age.message}</span>}

      <button type="submit">제출</button>
    </form>
  );
}

트랜스폼과 파싱

const numberString = z.string().transform((val) => parseInt(val));
const dateString = z.string().transform((val) => new Date(val));

const formDataSchema = z.object({
  userId: numberString,
  joinDate: dateString,
  preferences: z.string().transform((val) => JSON.parse(val))
});

비동기 검증

const asyncSchema = z.object({
  username: z.string().refine(async (value) => {
    const response = await fetch(`/api/check-username/${value}`);
    return response.ok;
  }, "이미 사용 중인 사용자 이름입니다")
});

// 사용 예시
async function validateUsername(data) {
  try {
    await asyncSchema.parseAsync(data);
    return true;
  } catch (error) {
    return false;
  }
}

 

장점

  1. 타입 안전성
    • TypeScript와 완벽한 통합
    • 런타임 타입 검증
  2. 유연성
    • 복잡한 스키마 정의 가능
    • 커스텀 검증 로직 지원
  3. 사용 편의성
    • 직관적인 API
    • 체이닝 방식의 검증 규칙
  4. 성능
    • 효율적인 검증 처리
    • 최적화된 타입 추론

Zod는 TypeScript 프로젝트에서 데이터 검증을 위한 강력한 도구로, 특히 폼 처리와 API 응답 검증에 매우 유용합니다.