zod
Zod (Typescript Schema Validator)
TypeScript ํ๊ฒฝ์์ ๊ฐ์ฒด ๊ตฌ์กฐ์ ๋ฐ์ดํฐ ์ ํจ์ฑ์ ๊ฒ์ฆํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
๋ถํ์คํ ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์์์ ๊ฒ์ฆํ์ฌ ํ์ ๋ถ์ผ์น๋ก ์ธํ ์ค๋ฅ๋ฅผ ์ฌ์ ์ ๋ฐฉ์งํจ
Schema(์คํค๋ง): ๊ฐ์ฒด์ ์ค๊ณ๋
๋ฐํ์ ๊ฒ์ฆ: ํ์ ์คํฌ๋ฆฝํธ๋ ์ปดํ์ผ ํ์์์ ํ์ ์ ์ฒดํฌํ์ง๋ง, ๋ฐํ์์์ ๋ค์ด์ค๋ ์ธ๋ถ ๋ฐ์ดํฐ๊น์ง ์์ ํ๊ฒ ๊ฒ์ฆ ๊ฐ๋ฅ
์ธ๋ถ JSON, API ์๋ต, ์ฌ์ฉ์ ์ ๋ ฅ ๋ฑ ๋ถํ์คํ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ๋ค๋ฃจ๋ ๋ฐ ํ์
Zod ๋ฉ์๋ ํต์ฌ ๋ถ๋ฅ
1. ๊ฐ์ฒด/ํ์
์ ์
z.object({...})
๊ฐ์ฒด ๊ตฌ์กฐ ์ ์
z.object({ name: z.string() })
z.string()
, z.number()
, z.boolean()
๊ธฐ๋ณธ ํ์ ์ ์
z.string().min(3)
z.array(schema)
๋ฐฐ์ด ๊ฒ์ฆ
z.array(z.string())
z.enum([...])
๋ฆฌํฐ๋ด ๊ฐ ์งํฉ ๊ฒ์ฆ
z.enum(['A','B'])
z.literal(value)
ํน์ ๊ฐ๋ง ํ์ฉ
z.literal('admin')
2. ๊ฐ ๊ฒ์ฆ / ์กฐ๊ฑด๋ถ ๊ฒ์ฆ
.min()
, .max()
์ต์/์ต๋ ๊ธธ์ด, ๊ฐ ๊ฒ์ฆ
z.string().min(3)
.email()
, .url()
ํฌ๋งท ๊ฒ์ฆ
z.string().email()
.refine(predicate, { message })
์ปค์คํ ์กฐ๊ฑด ๊ฒ์ฆ
z.string().refine(val => val.includes('@'), { message: '์ด๋ฉ์ผ ์๋' })
.optional()
์ ํ์ ํ๋
z.string().optional()
.nullable()
null ํ์ฉ
z.string().nullable()
3. ๊ฐ ๋ณํ / ๊ฐ๊ณต
.transform(fn)
๊ฒ์ฆ๋ ๊ฐ์ ๋ค๋ฅธ ํํ๋ก ๋ณํ (sync/async)
z.string().transform(val => Number(val))
.coerce.*()
์์ ํ์ ๊ฐ์ ๋ณํ
z.coerce.number()
(๋ฌธ์์ด โ ์ซ์)
.catch(defaultValue)
์คํจ ์ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋์ฒด
z.number().min(0).catch(0)
4. ํ์ฑ / ์์ ํ ์ฒ๋ฆฌ
.parse(value)
๊ฐ ๊ฒ์ฆ + ๋ณํ, ์คํจ ์ ์์ธ throw
schema.parse(input)
.safeParse(value)
๊ฐ ๊ฒ์ฆ + ๋ณํ, ์คํจ ์ { success, error }
๋ฐํ
schema.safeParse(input)
.parseAsync(value)
async transform ํฌํจ ์ ์ฌ์ฉ
await schema.parseAsync(input)
.safeParseAsync(value)
async transform + ์์ ์ฒ๋ฆฌ
await schema.safeParseAsync(input)
5. ํ์
์ถ๋ก
z.infer<typeof schema>
์คํค๋ง ๊ธฐ๋ฐ TypeScript ํ์ ์ถ๋ก
type User = z.infer<typeof userSchema>
Error
ZodError ๊ตฌ์กฐ๋ฅผ ํตํด ์ด๋ค ํ๋๊ฐ ์ ์คํจํ๋์ง ํ์ธ ๊ฐ๋ฅ
.errors
: ๊ฐ ํ๋๋ณ ์๋ฌ ์ ๋ณด.message
: ๊ธฐ๋ณธ ์๋ฌ ๋ฉ์์ง
const schema = z.string().min(5, { message: "์ต์ 5๊ธ์ ํ์" });
const result = schema.safeParse("abc");
if (!result.success) console.log(result.error.errors);
ZodError ๋ฐ์ดํฐ ๊ตฌ์กฐ
{
issues: [
{
code: 'invalid_type', // ์๋ฌ ์ฝ๋. Zod ๋ด๋ถ์์ ์ ์ํ ์๋ฌ ์ ํ
expected: 'string', // ์์ ํ์
๋๋ ๊ฐ
received: 'undfeind', // ์ค์ ๋ค์ด์จ ๊ฐ
path: [Array], // ๋ฌธ์ ๊ฐ ๋ฐ์ํ ํ๋ ๊ฒฝ๋ก. ์ต์์ ํ๋๋ []๋ก ํ์
message: 'Not a string!' // ์ปค์คํ
/๊ธฐ๋ณธ ์๋ฌ ๋ฉ์์ง
}
],
// ๋ฐํ์์ ์๋ฌ๋ฅผ ์ถ๊ฐํ ๋ ์ฌ์ฉ๋๋ ๋ฉ์๋
addIssue: [Function (anonymous)],
addIssues: [Function (anonymous)],
// ๋ด๋ถ์ ์ผ๋ก issues์ ๋์ผ
errors: [
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [Array],
message: 'Not a string!'
}
]
}
์๋ฌ ๊ด๋ จ ๋ฉ์๋
.refine()
์ปค์คํ ์กฐ๊ฑด ๊ฒ์ฆ์ ์ถ๊ฐํ ๋ ์ฌ์ฉ
์กฐ๊ฑด์ ๋ง์กฑํ์ง ์์ผ๋ฉด ์ง์ ํ ๋ฉ์์ง๋ก ์คํจ ์ฒ๋ฆฌ
.catch()
๊ฒ์ฆ ์คํจ ์ ๊ธฐ๋ณธ๊ฐ์ ๋ฐํํ๋๋ก ์ฒ๋ฆฌ
์์ธ๋ฅผ ๋์ง์ง ์๊ณ ์์ ํ๊ฒ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋์ฒด ๊ฐ๋ฅ
.message()
๊ธฐ๋ณธ ์ ๊ณต๋๋ ์๋ฌ ๋ฉ์์ง๋ฅผ ์ปค์คํ ํ ๋ ์ฌ์ฉ
๋๋ถ๋ถ
.min()
,.max()
,.email()
๋ฑ ๋ฉ์๋์ ์ต์ ์ผ๋ก ์ ๋ฌ
transform
๊ฒ์ฆํ ๊ฐ(validated value)์ ์ํ๋ ํํ๋ก ๋ณํํ ๋ ์ฌ์ฉ
์ธ๋ถ ๋ฐ์ดํฐ๊ฐ ๊ฒ์ฆ์ ๋์ง๋ง, ์ฑ ๋ด๋ถ์์ ๋ค๋ฅธ ํํ๋ก ์ฌ์ฉํด์ผํ ๋
Ex: ๋ฌธ์์ด โ ์ซ์, ๋ ์ง ๋ฌธ์์ด โ
Date
๊ฐ์ฒด, API ์๋ต ํฌ๋งท ๋ณํ ๋ฑvalidate โ ๋ณํ โ ์ฌ์ฉ ์์๋ฅผ ํ ์ค๋ก ์ค์ผ ์ ์์
async ๋น๋๊ธฐ ํจ์๋ ์ง์ํจ
async transform์ ์ธ ๊ฒฝ์ฐ,
parseAsync()
๋๋safeParseAsync()
๋ฅผ ์ฌ์ฉํด์ผ ํจ
const dateSchema = z.string().transform(str => new Date(str));
const date = dateSchema.parse("2025-08-17T00:00:00Z");
console.log(date instanceof Date); // true
๊ตญ์ ํ (i18n)
Zod ์๋ฌ ๋ฉ์์ง๋ฅผ ๊ตญ์ ํํ ์ ์์
lib/i18n-zod.ts
์ ์ค์ ์ ๋๊ณ , ํ๋ก์ ํธ ์ ์ญ์์import { z } from '@/lib/i18n-zod'
๋ก ์ฌ์ฉ์ฌ์ฉ์ ์์ฒญ ํค๋(
Accept-Language
)์ ๋ฐ๋ผ ๋์ ์ผ๋ก ์ธ์ด๋ฅผ ๋ฐ๊ฟ์ ์ฌ์ฉ ๊ฐ๋ฅ
import i18next from 'i18next';
import { z } from 'zod';
import { zodI18nMap } from 'zod-i18n-map'
// import your language translation files
import translation from 'zod-i18n-map/locales/ko/zod.json';
i18next.init({
lng: 'ko',
resources: {
ko: { zod: translation },
},
});
z.setErrorMap(zodI18nMap);
export { z };
ETC
API ์๋ต ๊ฒ์ฆ
์ธ๋ถ API์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๋ ํญ์
safeParse
์ฌ์ฉ์์ธ๋ก ์ฑ์ด ํฐ์ง๋ ๊ฒ์ ๋ฐฉ์ง ๊ฐ๋ฅ
TypeScript ํ์ ๊ณผ ์ฐ๊ณ
z.infer<typeof schema>
๋ก ํ์ ์ถ๋ก โ ํ์ ์ ์ ์ค๋ณต ๋ฐฉ์ง
Nested Object ๊ฒ์ฆ
๊ฐ์ฒด ์ ๊ฐ์ฒด ๊ตฌ์กฐ๋
.object
์์.object
๋ฅผ ์ค์ฒฉํด ์ฌ์ฉ ๊ฐ๋ฅ
Optional & Default
optional()
๊ณผdefault()
๋ฅผ ์กฐํฉํ์ฌ ๋๋ฝ ํ๋ ์ฒ๋ฆฌ ๊ฐ๋ฅ
Coercion
์ซ์๊ฐ ๋ฌธ์์ด๋ก ์ค๋ API ์๋ต ์
.coerce.number()
์ฌ์ฉ ๊ฐ๋ฅ
Async ๊ฒ์ฆ
๋น๋๊ธฐ ๊ฒ์ฆ ๋ก์ง ํ์ ์
z.preprocess
+ async ๊ฐ๋ฅ
Last updated