Coerce 및 Validator 모듈

Odyssey·2025년 2월 15일
0

Next.js_study

목록 보기
33/58
post-thumbnail

2025.2.15 토요일의 공부기록

Zod의 coerce(강제 변환) 기능을 사용하면, 입력된 값을 원하는 타입으로 변환할 수 있다.
또한, validator 모듈을 활용하면 이메일, URL, 날짜 등의 데이터 유효성을 더욱 강화할 수 있다.

📌 공식 문서 참고:


z.coerce를 활용한 데이터 타입 변환

coerce다양한 원시 타입의 변환을 지원하며, 입력값을 자동으로 변환하여 검증할 수 있도록 도와준다.

📌 기본 예제: coerce를 사용한 타입 변환

import { z } from "zod";

const numberSchema = z.coerce.number();
const booleanSchema = z.coerce.boolean();
const dateSchema = z.coerce.date();

console.log(numberSchema.parse("123"));   // ✅ 123 (문자열 → 숫자 변환)
console.log(booleanSchema.parse("true")); // ✅ true (문자열 → 불리언 변환)
console.log(dateSchema.parse("2024-01-01")); // ✅ 2024-01-01T00:00:00.000Z (문자열 → Date 변환)

📌 설명:

  • .coerce.number()"123"(문자열)을 123(숫자)로 변환.
  • .coerce.boolean()"true"(문자열)을 true(불리언)로 변환.
  • .coerce.date()"2024-01-01"(문자열)을 Date 객체로 변환.

📌 예제 2: 숫자로 변환 후 검증

const positiveNumberSchema = z.coerce.number().refine((val) => val > 0, {
  message: "숫자는 0보다 커야 합니다.",
});

console.log(positiveNumberSchema.parse("10"));  // ✅ 10
console.log(positiveNumberSchema.safeParse("-5"));  // ❌ 실패 (음수는 허용되지 않음)

📌 설명:

  • .coerce.number() → 입력값을 숫자로 변환 후, 0보다 큰 값인지 검증.

Validator.js를 활용한 문자열 검증

Validator.js는 이메일, URL, 날짜 형식 검증XSS 방지(sanitization) 기능을 제공하는 라이브러리이다.

📌 Validator.js 설치

npm i validator
npm i --save-dev @types/validator  # TypeScript 지원

📌 예제 1: 이메일 검증 (isEmail())

import validator from "validator";

console.log(validator.isEmail("test@example.com")); // ✅ true
console.log(validator.isEmail("invalid-email"));   // ❌ false

📌 설명:

  • validator.isEmail() → 이메일 형식 검증.

📌 예제 2: URL 검증 (isURL())

console.log(validator.isURL("https://example.com")); // ✅ true
console.log(validator.isURL("invalid-url"));        // ❌ false

📌 설명:

  • validator.isURL() → 올바른 URL 형식인지 검증.

📌 예제 3: 문자열에서 HTML 태그 제거 (stripLow())

console.log(validator.stripLow("<script>alert('XSS')</script>"));

📌 설명:

  • stripLow()를 사용하여 XSS 공격 방지.

coerce + validator를 활용한 Next.js 데이터 검증

📌 createAccount 서버 액션 (회원가입 검증)

"use server";

import { z } from "zod";
import validator from "validator";

// **회원가입 스키마 정의**
const registerSchema = z.object({
  username: z.string().trim().min(5, "사용자 이름은 최소 5자 이상이어야 합니다.").max(10, "사용자 이름은 최대 10자 이하이어야 합니다."),
  email: z.string().email("올바른 이메일 형식을 입력하세요.").toLowerCase(),
  password: z.string().min(8, "비밀번호는 최소 8자 이상이어야 합니다.")
    .regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*?[#?!@$%^&*-]).+$/, { message: "비밀번호는 대문자, 소문자, 숫자, 특수문자를 포함해야 합니다." }),
  birthdate: z.coerce.date(),
  phone: z.string().refine((val) => validator.isMobilePhone(val, "ko-KR"), {
    message: "올바른 전화번호 형식이 아닙니다. (예: 010-1234-5678)",
  }),
}).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(),
    birthdate: formData.get("birthdate")?.toString(),
    phone: formData.get("phone")?.toString(),
  };

  // **유효성 검사 실행**
  const validationResult = registerSchema.safeParse(data);

  if (!validationResult.success) {
    return {
      success: false,
      message: validationResult.error.issues[0].message, // 첫 번째 오류 메시지 반환
    };
  }

  return { success: true, message: "회원가입 성공!" };
}

📌 설명:

  • .coerce.date()문자열 생년월일을 Date 타입으로 변환.
  • .refine((val) => validator.isMobilePhone(val, "ko-KR"))한국 전화번호 형식 검증.
  • .trim(), .toLowerCase()공백 제거, 이메일 소문자 변환.

📌 클라이언트에서 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 />
        <input name="birthdate" type="date" placeholder="Birthdate" className="border p-2" required />
        <input name="phone" type="text" placeholder="Phone Number" 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>
  );
}

📌 더 자세한 내용은 Zod 공식 문서 & Validator.js를 참고하자! 🚀

0개의 댓글