--- id: ts-schema-validation-comparison title: Schema 검증 비교 — Zod / Valibot / Effect Schema / ArkType category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [typescript, validation, zod, vibe-coding] tech_stack: { language: "TS", applicable_to: ["Backend", "Frontend"] } applied_in: [] aliases: [Zod, Valibot, Effect Schema, ArkType, Yup, runtime validation, schema] --- # Schema Validation 비교 > Runtime 검증 + TS infer = 표준. **Zod = 가장 일반, Valibot = 작은 bundle, ArkType = 빠르고 syntax 신선, Effect Schema = Effect 사용자**. ## 📖 핵심 개념 - 검증: unknown → typed parse / fail. - Infer: schema → TS type. - Refinement: 추가 조건 (email, regex). - Transform: parse 시 변환. ## 💻 코드 패턴 ### Zod (de-facto 표준) ```ts import { z } from 'zod'; const User = z.object({ id: z.string().uuid(), email: z.string().email(), age: z.number().int().positive().optional(), role: z.enum(['admin', 'user']).default('user'), tags: z.array(z.string()).default([]), }); type User = z.infer; const parsed = User.parse(input); // throws ZodError const safe = User.safeParse(input); // { success, data | error } ``` ```ts // transform const Trimmed = z.string().transform(s => s.trim()); // refine const StrongPw = z.string().refine(s => s.length >= 8 && /[0-9]/.test(s)); // discriminated union const Action = z.discriminatedUnion('type', [ z.object({ type: z.literal('a'), x: z.number() }), z.object({ type: z.literal('b'), y: z.string() }), ]); ``` ### Valibot (작은 bundle, tree-shakable) ```ts import * as v from 'valibot'; const User = v.object({ id: v.pipe(v.string(), v.uuid()), email: v.pipe(v.string(), v.email()), age: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0))), }); type User = v.InferOutput; const parsed = v.parse(User, input); ``` → Bundle: zod ~13KB vs valibot ~2KB. ### ArkType (빠른 + syntax) ```ts import { type } from 'arktype'; const User = type({ id: 'string', email: 'email', age: 'number > 0?', role: '"admin" | "user"', }); type User = typeof User.infer; const out = User(input); if (out instanceof type.errors) console.log(out.summary); else console.log(out); ``` → TS-template-literal 기반 — runtime 빠름, dev 시 type 직접 추적. ### Effect Schema ```ts import { Schema } from 'effect'; const User = Schema.Struct({ id: Schema.String.pipe(Schema.uuid()), email: Schema.String.pipe(Schema.email()), age: Schema.Number.pipe(Schema.positive()), }); const decoded = Schema.decodeUnknownSync(User)(input); ``` → Effect 와 통합. ### 공통 패턴 #### Form (RHF) ```ts import { zodResolver } from '@hookform/resolvers/zod'; useForm({ resolver: zodResolver(schema) }); ``` #### API (Hono) ```ts import { zValidator } from '@hono/zod-validator'; app.post('/users', zValidator('json', User), (c) => { const data = c.req.valid('json'); // typed }); ``` #### 환경변수 ```ts const Env = z.object({ DATABASE_URL: z.string().url(), PORT: z.coerce.number().default(3000), NODE_ENV: z.enum(['dev', 'prod', 'test']), }); export const env = Env.parse(process.env); ``` #### LLM structured output ```ts const Recipe = z.object({...}); zodResponseFormat(Recipe, 'recipe'); // OpenAI zodToJsonSchema(Recipe); // Anthropic ``` ### Migration zod → valibot ```ts // 비슷한 API — 직접 변경 z.object({ name: z.string() }) v.object({ name: v.string() }) z.string().email() v.pipe(v.string(), v.email()) ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | 일반 (백 + 프론트) | Zod | | Frontend bundle critical | Valibot | | 성능 critical (validation hot path) | ArkType | | Effect 사용 중 | Effect Schema | | 학습 / 안정성 | Zod | | Shared backend + frontend | Zod (가장 호환) | ## ❌ 안티패턴 - **검증 없이 unknown 그대로 사용**: 런타임 crash. - **Zod schema 가 거대 (50+ 필드)**: 분리 + compose. - **Refinement 안에 외부 fetch**: synchronous expected. transform. - **`.passthrough()` 디폴트**: extra 키 안 차단. strict. - **Type 직접 정의 + schema 따로**: drift. infer. - **Form schema = API schema 직접**: 다를 수 있음 — 분리. - **zod + 큰 bundle 신경 X**: SSR 만 / API 만 사용. ## 🤖 LLM 활용 힌트 - Zod 가 안전 디폴트. - Bundle 작아야 = Valibot. - AI structured output = Zod (OpenAI helper). ## 🔗 관련 문서 - [[AI_Structured_Output_Zod]] - [[TS_Effect_FP_Patterns]]