Zod로 완성하는 Form 유효성 검사

JN·2025년 4월 19일
post-thumbnail

Zod란?

Zod는 티입스크립트를 위한 런타임 스키마 선언 및 검증 라이브러리이다.
티입스크립트만으로는 컴파일 타임에만 타입 검사를 하기 때문에, 실제 앱에서 유저가 입력하는 데이터는 검증되지 않는다.
Zod는 이 갭을 메우기 위한 도구로, 런타입 타입에 안정성을 확보하게 해준다.
클라이언트 사이드 뿐만 아니라 서버 사이드(NextJS)에서도 사용 가능하다!

기본 사용법

1. 스키마 정의

먼저 다음과 같이 데이터의 형태와 구조를 정의한다

import { z } from "zod";

const LoginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(6),
});

자주 사용하는 다양한 메서드들을 정리해보았다.

  • min(n):최소 길이 제한
  • max(n):최대 길이 제한
  • email():이메일 형식 검사
  • regex():정규식 패턴 검사
  • refine(fn, options):커스텀 유효성 로직
  • optional():선택 필드로 지정
  • nullable():null 허용
  • transform():전처리 및 후처리

2. 유효성 검사

이 스키마는 다음과 같은 메서드을 제공한다

  • .parse(data): 유효하지 않으면 오류 발생하여 코드가 중단된다
  • .safeParse(data): 유효성 결과를 명시적으로 반환(success, error 필드)하고 코드가 중단되지 않는다.

parse vs safeParse 차이

  • parse(): 실패 시 예외 발생 → try/catch 필요
  • safeParse(): 실패 시 에러 객체 반환 → if/else 처리

parse()

try {
  const data = LoginSchema.parse(input);
} catch (err) {
  // 실패 시 예외 던져짐
}

safeParse()

const result = LoginSchema.safeParse({
  email: "user@example.com",
  password: "123456",
});

if (!result.success) {
  console.log(result.error.format()); // 에러 메시지 보기
}

에러 프로퍼티를 추출하여 추가 검증 조회를 할 수 있다.

const result = SignupSchema.safeParse(input);

if (!result.success) {
  const fieldErrors = result.error.flatten().fieldErrors;
  console.log(fieldErrors); // 각 필드별 에러 메시지
}

사용 예시

다음은 Zod를 통해 실제 로그인 Form Validation을 구현한 간단한 예시이다.

1. Zod 스키마 정의

const LoginSchema = z.object({
  email: z.string().email("이메일 형식이 올바르지 않습니다."),
  password: z.string().min(6, "비밀번호는 6자 이상이어야 합니다."),
});

2. React Hook Form과 연동

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

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

const onSubmit = (data) => {
  console.log(data); // 유효성 검사를 통과한 값
};

3. UI 적용

<form onSubmit={handleSubmit(onSubmit)}>
  <input type="email" {...register("email")} placeholder="이메일" />
  {errors.email && <span>{errors.email.message}</span>}

  <input type="password" {...register("password")} placeholder="비밀번호" />
  {errors.password && <span>{errors.password.message}</span>}

  <button type="submit">로그인</button>
</form>

나는 회원가입 Form을 구현하면서 다음과 같이 스키마를 작성하였고, 효율적으로 Form 유효성을 관리할 수 있었다.

const SignupSchema = z
  .object({
    name: z.string().min(2, "이름은 2자 이상이어야 합니다."),
    email: z.string().email(),
    password: z.string().min(6),
    confirmPassword: z.string(),
    birthDate: z
      .string()
      .regex(/^\d{4}-\d{2}-\d{2}$/, "생년월일은 YYYY-MM-DD 형식이어야 합니다."),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: "비밀번호가 일치하지 않습니다.",
    path: ["confirmPassword"],
  });

타입 자동 추론

Zod의 진짜 매력 중 하나는 스키마를 정의하면 타입스크립트 타입이 자동으로 생성된다는 점이다.
즉, 별도로 interface나 type을 작성하지 않아도 된다.

const SignupSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  password: z.string().min(6),
});

// ✅ 타입 자동 추론
type SignupInput = z.infer<typeof SignupSchema>;

function handleSignup(data: SignupInput) {
  // data는 자동으로 { name: string; email: string; password: string } 타입
  console.log(data.name);
}

Zod의 infer를 사용하면 면 이미 정의한 스키마로 부터 타입을 뽑아낼 수 있다.

장점을 다시 정리하자면

  1. 중복 제거: 스키마 정의와 타입 정의를 따로 안 써도 된다.
  2. 정합성 유지: 스키마와 타입이 항상 일치한다.
  3. 리팩토링에 강함: 스키마만 수정하면 타입도 자동 반영된다.

zod를 왜 이제야 알게 되었을까?
타입에 대한 관리를 따로 안해도 되니 너무x10 편리했다.

profile
개발일지📒

0개의 댓글