⚖️ Side-by-Side Comparison
Code & Behavior Diff: Unvalidated vs Manual JS vs Zod
Submit identical form data to three different validation strategies and inspect the resulting runtime behavior, error output, and codebase complexity.
Lines of Validator Code
28 vs 120 (Manual)
-76% boilerplateType Drift Risk
0% (z.infer)
Single source of truthFormData Coercion
Native (z.coerce)
String $\to$ Number/DateNested Error Paths
Automatic
error.flatten()Inject:
📝 Live Form Input
Simulates user submission⚡ Mode 3: Zod SafeParse Output
Type-Safe & CoercedZod Parse Output & Types
{
"status": "Validation & Parsing Passed ✓",
"success": true,
"data": {
"username": "alex_dev",
"email": "alex@company.io",
"age": 24,
"website": "https://alex.dev",
"role": "developer",
"bio": "Building reactive tools in SvelteKit.",
"tags": [
"svelte",
"typescript",
"zod"
],
"preferences": {
"newsletter": true,
"theme": "dark",
"notifyOnReply": true
}
},
"computedAgeInFiveYears": 29,
"metadata": {
"linesOfSchemaCode": 28,
"typeInference": "type User = z.infer<typeof userSchema> (Zero drift)",
"coercionSuccess": "Converted string \"24\" -> number 24 automatically"
}
}📑 Codebase Comparison: Manual Implementation vs Zod Schema
Notice the difference in declarative conciseness, maintainability, and automatic type inference.
Manual Validation (~120 Lines) High maintenance & drift risk
typescript
1// 1. You must maintain this interface manually:2export interface ManualUser {3 username: string;4 email: string;5 age: number;6 // ... 8 more fields7}89// 2. ~120 lines of repetitive if/else checking:10export function validateUserManually(input: any) {11 const errors: Record<string, string[]> = {};12 13 if (!input.username || input.username.length < 3) {14 errors.username = ['Username too short'];15 }16 const age = Number(input.age);17 if (isNaN(age) || age < 18) {18 errors.age = ['Must be at least 18'];19 }20 // ... 80 more lines for email regex, url parsing,21 // enum checks, array loops, nested null-checks ...22 23 // 3. Danger: Unsafe type assertion24 return { success: Object.keys(errors).length === 0, data: input as ManualUser, errors };25}Zod Schema (~28 Lines) Single Source of Truth
typescript
1import { z } from 'zod';23// 1. One single declarative schema:4export const userSchema = z.object({5 username: z.string().trim().min(3).max(20).regex(/^[a-z0-9_]+$/),6 email: z.string().trim().email(),7 age: z.coerce.number().int().min(18).max(120),8 website: z.string().trim().url().optional().or(z.literal('')),9 role: z.enum(['admin', 'developer', 'designer', 'viewer']),10 bio: z.string().max(200).optional().default(''),11 tags: z.array(z.string().trim().min(1)).min(1).max(5),12 preferences: z.object({13 newsletter: z.boolean().default(false),14 theme: z.enum(['light', 'dark', 'system']).default('system'),15 notifyOnReply: z.boolean().default(true)16 })17});1819// 2. TypeScript type generated automatically (Zero Drift!):20export type User = z.infer<typeof userSchema>;2122// 3. One-line safe execution:23const result = userSchema.safeParse(formData);