Afaik

10월 1일

오늘 배운 것 (TIL)

Zod를 활용한 TypeScript 타입 안전성과 런타임 유효성 검증

핵심 요약 (TL;DR)

Zod는 TypeScript-first 스키마 선언 및 유효성 검증 라이브러리로, 정적 타입과 런타임 검증을 동시에 제공하여 외부 데이터의 안전성을 보장한다.

Zod란?

TypeScript-first 스키마 선언 및 유효성 검증 라이브러리

  • 정적 타입 추론과 런타임 유효성 검증을 동시에 제공
  • API 응답, 사용자 입력 등 외부 데이터 검증에 최적화
  • Zero dependencies (의존성 없음)
  • 직관적인 체이닝 API

기본 사용법

1. 설치

npm install zod
# or
pnpm add zod

2. 기본 스키마 정의

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 다른 검증 라이브러리

특징ZodYupJoi
TypeScript 우선⚠️ (지원)
타입 추론⚠️
Zero dependencies
번들 크기작음 (12kb)중간
체이닝 API
런타임 검증

참고 자료

Edit on GitHub

Last updated on