Zod를 활용한 유효성 검증

Odyssey·2025년 2월 9일
0

Next.js_study

목록 보기
30/58
post-thumbnail

2025.2.9 일요일의 공부기록

Zod정적 타입 추론을 제공하는 TypeScript 스키마 검증 라이브러리로,
유효성 검사(Type Validation)와 데이터 변환(Data Parsing)을 쉽게 할 수 있도록 도와준다.

📌 공식 문서:


Zod 설치 및 기본 사용법

📌 Zod 설치

npm install zod

📌 기본 예제: .parse() 사용하여 데이터 검증

import { z } from "zod";

// 문자열 스키마 생성
const stringSchema = z.string();

console.log(stringSchema.parse("fish")); // ✅ 유효 → "fish"
console.log(stringSchema.parse(12));     // ❌ 오류 발생 (숫자는 허용되지 않음)

📌 설명:

  • .parse()를 사용하면 데이터가 유효하면 그대로 반환하고, 유효하지 않다면 오류가 발생한다.

Zod를 활용한 Next.js 실습

📌 실습 코드 (createAccount 함수)

"use server";

import { z } from "zod";

// **Zod 스키마 정의**
const usernameSchema = z.string().min(5).max(10);

export async function createAccount(prevState: any, formData: FormData) {
  const data = {
    username: formData.get("username"),
    email: formData.get("email"),
    password: formData.get("password"),
    confirm_password: formData.get("confirm_password"),
  };

  // **유효성 검사**
  usernameSchema.parse(data.username);
}

📌 설명:

  • usernameSchema를 사용하여 username최소 5자, 최대 10자인지 검사.
  • .parse()를 사용하면 검증 실패 시 오류가 발생하여 코드 실행이 중단됨.

Zod 활용 예제: 회원가입 폼 검증

📌 1️⃣ 스키마 확장 (비밀번호 일치 검사 포함)

const registerSchema = z.object({
  username: z.string().min(5).max(10),
  email: z.string().email(),
  password: z.string().min(6),
  confirm_password: z.string().min(6),
}).refine((data) => data.password === data.confirm_password, {
  message: "비밀번호가 일치하지 않습니다.",
  path: ["confirm_password"],
});

📌 설명:

  • .object()를 사용하여 여러 필드를 동시에 검사.
  • .email() → 이메일 형식 검증.
  • .min(6) → 비밀번호 최소 6자 이상.
  • .refine()을 사용하여 비밀번호 & 비밀번호 확인이 일치하는지 검사.

📌 2️⃣ createAccount 함수에서 검증 적용

"use server";

import { z } from "zod";

const registerSchema = z.object({
  username: z.string().min(5).max(10),
  email: z.string().email(),
  password: z.string().min(6),
  confirm_password: z.string().min(6),
}).refine((data) => data.password === data.confirm_password, {
  message: "비밀번호가 일치하지 않습니다.",
  path: ["confirm_password"],
});

export async function createAccount(prevState: any, formData: FormData) {
  const data = {
    username: formData.get("username")?.toString(),
    email: formData.get("email")?.toString(),
    password: formData.get("password")?.toString(),
    confirm_password: formData.get("confirm_password")?.toString(),
  };

  try {
    registerSchema.parse(data);
    return { success: true, message: "회원가입 성공!" };
  } catch (error) {
    return { success: false, message: error.errors[0].message };
  }
}

📌 설명:

  • .parse()를 try-catch로 감싸서 오류 발생 시 메시지 반환.
  • error.errors[0].message를 사용하여 첫 번째 검증 오류 메시지를 출력.

📌 3️⃣ 클라이언트에서 useActionState로 데이터 검증하기

"use client";

import { useActionState } from "react";
import { createAccount } from "@/server-actions";

export default function RegisterForm() {
  const [state, formAction, isPending] = useActionState(createAccount, { success: null, message: "" });

  return (
    <div className="flex flex-col gap-10 py-8 px-6">
      <h1 className="text-2xl">회원가입</h1>
      <form action={formAction} className="flex flex-col gap-3">
        <input name="username" type="text" placeholder="Username" className="border p-2" required />
        <input name="email" type="email" placeholder="Email" className="border p-2" required />
        <input name="password" type="password" placeholder="Password" className="border p-2" required />
        <input name="confirm_password" type="password" placeholder="Confirm Password" className="border p-2" required />
        <button type="submit" disabled={isPending} className="bg-blue-500 text-white px-4 py-2 rounded-md">
          {isPending ? "가입 중..." : "가입하기"}
        </button>
      </form>
      {state.message && (
        <p className={state.success ? "text-green-500" : "text-red-500"}>{state.message}</p>
      )}
    </div>
  );
}

📌 설명:

  • useActionState()를 사용하여 서버 액션(createAccount)의 상태를 자동으로 관리.
  • isPending을 활용하여 폼 제출 중 버튼을 비활성화.
  • state.message를 출력하여 회원가입 성공/실패 메시지를 표시.

Zod의 다른 주요 기능

1️⃣ .safeParse(): 오류 발생 없이 유효성 검사

const result = registerSchema.safeParse({
  username: "user",
  email: "invalid-email",
  password: "123456",
  confirm_password: "123456",
});

if (!result.success) {
  console.log(result.error.format());
}

📌 설명:

  • .parse()는 유효하지 않으면 오류를 던지지만,
  • .safeParse()는 오류가 발생해도 실패 정보를 객체로 반환.

2️⃣ .strict(): 예상치 못한 값이 포함된 경우 오류 발생

const userSchema = z.object({
  name: z.string(),
  age: z.number(),
}).strict();

userSchema.parse({ name: "John", age: 25, extra: "unexpected" }); // ❌ 오류 발생

📌 설명:

  • .strict()을 사용하면 정의되지 않은 값이 포함될 경우 오류 발생.

3️⃣ .transform(): 데이터 변환

const nameSchema = z.string().transform((val) => val.toUpperCase());

console.log(nameSchema.parse("john")); // "JOHN"

📌 설명:

  • .transform()을 사용하여 데이터를 자동 변환 가능.

결론: Next.js에서 Zod를 활용하면?

✅ 기능🛠 설명
유효성 검사.parse() 또는 .safeParse() 사용
폼 데이터 검증useActionState() + Zod
비밀번호 일치 검사.refine() 사용
자동 변환.transform() 사용

0개의 댓글