본문 바로가기

클라이언트/Next.js
[Next.js] Zod를 사용하여 데이터 검증하기

// Zod

- 타입스크립트의 타입 안정성을 향상시켜주는TypeScript-first 스키마 선언 및 검증 라이브러리

- 공식 문서: https://zod.dev/

 

GitHub - colinhacks/zod: TypeScript-first schema validation with static type inference

TypeScript-first schema validation with static type inference - colinhacks/zod

github.com


 설치

npm install zod

▪ 스키마 정의 및 검증

import { z } from 'zod';

const 스키마명 = z.자료형({
  데이터명: z.자료형().검증메소드(),
});

const 스키마명 = z.자료형().검증메소드();




스키마명.parse(검증할데이터);

스키마에 위반되는 입력이 들어오면 런타임 에러가 발생한다.

'use server';
import { z } from '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);
}

'use server';
import { z } from 'zod';

const formSchema = z.object({
  username: z.string().min(3).max(10),
  email: z.string().email(),
  password: z.string().min(10),
  confirm_password: z.string().min(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'),
  };

  formSchema.parse(data);
}

▪ safeParse

safeParse를 사용하여 검증하면 에러를 throw 하지 않는다.

데이터가 유효한 경우 success의 값이 true, 유효하지 않은 경우 false이다.

또한 유효하지 않은 경우에는 에러 정보(error)도 함께 반환한다.


'use server';
import { z } from 'zod';

const formSchema = z.object({
  username: z.string().min(3).max(10),
  email: z.string().email(),
  password: z.string().min(10),
  confirm_password: z.string().min(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'),
  };
  const result = formSchema.safeParse(data);
  if (!result.success) {
    return result.error.flatten();
  }
}

 

'use client';

import FormInput from '@/components/form-input';
import FormButton from '@/components/form-btn';
import SocialLogin from '@/components/social-login';
import { useFormState } from 'react-dom';
import { createAccount } from '@/app/create-account/actions';

function CreateAccount() {
  const [state, action] = useFormState(createAccount, null);

  return (
    <div className="flex flex-col gap-10 py-8 px-6">
      <div className="flex flex-col gap-2">
        <h1 className="font-custom-bold">회원가입</h1>
        <h2>Fill in the form below to join!</h2>
      </div>
      <form action={action} className="flex flex-col gap-3">
        <FormInput
          name="username"
          type="text"
          placeholder="Username"
          required
          errors={state?.fieldErrors.username}
        />
        <FormInput
          name="email"
          type="email"
          placeholder="Email"
          required
          errors={state?.fieldErrors.email}
        />
        <FormInput
          name="password"
          type="password"
          placeholder="Password"
          required
          errors={state?.fieldErrors.password}
        />
        <FormInput
          name="confirm_password"
          type="password"
          placeholder="Confirm Password"
          required
          errors={state?.fieldErrors.confirm_password}
        />
        <FormButton text="Create account" />
      </form>
      <SocialLogin />
    </div>
  );
}

export default CreateAccount;

// 메시지 커스텀

- 자료형이 잘못되었을 때

const 스키마명 = z.object({
  필드명: z
    .string({
      invalid_type_error: '메시지',
    })
});

 

- 필수값을 입력하지 않았을 때

const 스키마명 = z.object({
  필드명: z
    .string({
      required_error: '메시지',
    })
});



- 길이 조건을 벗어났을 때

const 스키마명 = z.object({
  필드명: z
    .min(길이, '메시지')
});

// refine

- 사용자 정의 검증 메소드

const 스키마명 = z.object({
  필드명: z
    .refine(조건(true || false 반환), '메시지')
});

const formSchema = z.object({
  username: z
    .string({
      invalid_type_error: 'Username must be a string!',
      required_error: 'Write down your username',
    })
    .min(3, 'Username is too short.')
    .max(10)
    .refine((username) => !username.includes('potato'), 'No potatoes allowed'),
  email: z.string().email(),
  password: z.string().min(10),
  confirm_password: z.string().min(10),
});



※ form 전체에 해당하는 refine을 정의한 후 특정 부분에 error를 보낼 수도 있다.

const 스키마명 = z.object({
  필드명: z
  ...
})
.refine(조건 || 함수, {
   message: '메시지'
   path: ['필드name']
});

// regex

정규표현식을 사용하여 입력을 검증할 수도 있다.

const 스키마명 = z.object({
  필드명: z
    .regex(정규표현식, '메시지')
});

'use server';
import { z } from 'zod';

const passwordRegex = new RegExp(
  /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).+$/,
);

const formSchema = z
  .object({
    password: z
      .string()
      .min(10)
      .regex(
        passwordRegex,
        'A password must have lowercase, UPPERCASE, a number and special characters. ',
      ),
    confirm_password: z.string().min(10),
  })

export async function createAccount(prevState: any, formData: FormData) {
  const data = {
    password: formData.get('password'),
    confirm_password: formData.get('confirm_password'),
  };
  const result = formSchema.safeParse(data);
  if (!result.success) {
    return result.error.flatten();
  }
}

 


// coerce

- 강제로 형변환을 시켜준다.

- formData로 받는 데이터 타입을 모두 string인데, string이 아닌 다른 자료형(원래 자료형)으로 검증하기 위해 사용한다.

const 스키마명 = z.coerce.원하는자료형().검증메소드()

const tokenSchema = z.coerce.number().min(100000).max(999999);