๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Zod

2025. 12. 3. 15:40ยท๐Ÿ“‚ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

Zod

 

 

โœ… Zod์ด๋ž€?


์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ/ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ํ™˜๊ฒฝ์—์„œ “๋ฐ์ดํ„ฐ ๊ฒ€์ฆ(Validation)”๊ณผ “ํƒ€์ž… ์•ˆ์ „(Type Safety)”์„ ๋™์‹œ์— ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ์Šคํ‚ค๋งˆ ๊ธฐ๋ฐ˜ ๊ฒ€์ฆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

- ์‚ฌ์šฉ์ž๊ฐ€ Server Action์œผ๋กœ ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ๋„์›€์„ ์คŒ.

 

โœ… ์‚ฌ์šฉ๋ฒ•


์„ค์น˜

npm i zod

 

์˜ˆ์‹œ(/create-account/actions.ts)

"use server";

import bcrypt from "bcrypt";
import {
  PASSWORD_MIN_LENGTH,
  PASSWORD_REGEX,
  PASSWORD_REGEX_ERROR,
} from "@/lib/constants";
import db from "@/lib/db";
import z from "zod";
import { redirect } from "next/navigation";
import getSession from "@/lib/session";

const checkUsername = (username: string) => !username.includes("potato");

const checkPasswords = ({
  password,
  confirm_password,
}: {
  password: string;
  confirm_password: string;
}) => password === confirm_password;

const formSchema = z
  .object({
    username: z
      .string("Username must be a string!")
      .toLowerCase()
      .trim()
      .nonempty("Where is my username???")
      // invalid_type_error, required_error is deleted in string()
      /*     .min(3, {
      error: (issue) => {
        if (issue.code === "too_small") {
          return `Value must be > ${issue.minimum}`;
        }
      },
    }) */
      // .transform((username) => `๐Ÿ”ฅ${username}๐Ÿ”ฅ`)
      .refine(checkUsername, "No potatoes allowed!"),
    email: z.email().toLowerCase(),
    password: z.string().min(PASSWORD_MIN_LENGTH),
    // .regex(PASSWORD_REGEX, PASSWORD_REGEX_ERROR),
    confirm_password: z.string().min(PASSWORD_MIN_LENGTH),
  })
  .superRefine(async (data, ctx) => {
    const usernameExists = await db.user.findUnique({
      where: { username: data.username },
      select: { id: true },
    });
    if (usernameExists) {
      ctx.addIssue({
        code: "custom",
        message: "This username is already taken",
        path: ["username"],
      });
      return;
    }
    const emailExists = await db.user.findUnique({
      where: { email: data.email },
      select: { id: true },
    });
    if (emailExists) {
      ctx.addIssue({
        code: "custom",
        message: "This email is already taken",
        path: ["email"],
      });
      return;
    }
    if (!checkPasswords(data)) {
      ctx.addIssue({
        code: "custom",
        message: "Both passwords should be the same!",
        path: ["confirm_password"],
      });
      return;
    }
  });

export async function createAccount(prevState: unknown, formData: FormData) {
  const data = {
    username: formData.get("username"),
    email: formData.get("email"),
    password: formData.get("password"),
    confirm_password: formData.get("confirm_password"),
  };
  const result = await formSchema.safeParseAsync(data);
  if (!result.success) {
    const flatten = z.flattenError(result.error);
    console.log(flatten);
    /*     const treeify = z.treeifyError(result.error);
    console.log(treeify); */
    return flatten;
  } else {
    const hashedPassword = await bcrypt.hash(result.data.password, 12);
    const user = await db.user.create({
      data: {
        username: result.data.username,
        email: result.data.email,
        password: hashedPassword,
      },
      select: {
        id: true,
      },
    });
    const session = await getSession();
    session.id = user.id;
    await session.save();
    redirect("/profile");
  }
}

 

โœ… Coerce


coerce(๊ฐ•์š”ํ•˜๋‹ค): number ๊ฐ’์ด input์— ์ž…๋ ฅ ๋˜์–ด์„œ formData๋กœ ๋„˜์–ด์˜ค๋ฉด string์œผ๋กœ ๋ณ€ํ™˜๋˜๊ธฐ ๋•Œ๋ฌธ์— coerce๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํƒ€์ž…์„ number๋กœ ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ๋‹ค.

ex)

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

 

 

โœ… ๊ณต์‹ ๋ฌธ์„œ


https://zod.dev/

'๐Ÿ“‚ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Realm  (0) 2026.02.24
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - TanStack Query(React Query)  (0) 2026.02.03
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - SWR  (0) 2025.07.05
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - NextAuth  (0) 2025.07.04
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Iron Session  (0) 2025.07.04
'๐Ÿ“‚ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Realm
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - TanStack Query(React Query)
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - SWR
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - NextAuth
j2yonghwa
j2yonghwa
Trying to be a fullstack developer ๐Ÿš€
  • j2yonghwa
    j2yonghwa
    j2yonghwa
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (156)
      • โฐ Daily WakaTime (1)
      • ๐Ÿ–๏ธ ๋…ธ๋งˆ๋“œ์ฝ”๋” (2)
      • ๐Ÿบ Dev Setup (3)
      • ๐Ÿ”ญ Tech Info (1)
      • ๐Ÿšซ Error (1)
      • ๐Ÿ“‚ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (23)
      • ♣๏ธ Next.js 14 (10)
      • ♠๏ธ Next.js 12 (20)
      • ๐Ÿ›ธ React Native (12)
      • ๐Ÿฆ‹ TypeScript (1)
      • ๐Ÿ Python (2)
      • ๐ŸŒŠ TailwindCSS (4)
      • ๐Ÿงฉ SQL (25)
      • ๐Ÿ’Ž Prisma (5)
      • ๐ŸŒฑ MongoDB (4)
      • ๐ŸŽฏ Redis (1)
      • ๐Ÿงฌ GraphQL (2)
      • ๐Ÿ”ฅ Firebase (7)
      • ๐Ÿ’ธ Third-Party Services (2)
      • ๐Ÿ•ธ๏ธ Web (1)
      • ๐Ÿ† ์ฝ”๋”ฉํ…Œ์ŠคํŠธ (23)
      • ๐Ÿ“™ ๋ชจ๋”ฅ๋‹ค (5)
      • ๐Ÿ“— ์ฝ”ํ…Œ ํ•ฉ๊ฒฉ์ž ๋˜๊ธฐ -JS- (0)
      • ๐Ÿ“˜ ํด๋ฆฐ์ฝ”๋“œ (0)
      • ๐Ÿฏ ๊ฟ€ํŒ ๐Ÿ (1)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ํƒœ๊ทธ
    • ๋ฐฉ๋ช…๋ก
  • ๋งํฌ

    • ๊นƒํ—™
  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
    ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ
    React Native
    ๋ชจ๋”ฅ๋‹ค
    react router
    dev setup
    Next.js
    SQL
    API
    MySQL
    tailwindcss
    Firebase
    ์ฝ”๋”ฉํ…Œ์ŠคํŠธ ์ž…๋ฌธ
    0๋ ˆ๋ฒจ
    next.js 14
    mongoDB
    Prisma
    Python
    next.js 12
    PostgreSQL
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
j2yonghwa
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Zod
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”