
โ 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);
โ ๊ณต์ ๋ฌธ์
'๐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ๋ผ์ด๋ธ๋ฌ๋ฆฌ - 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 |