⚖️ 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% boilerplate
Type Drift Risk
0% (z.infer)
Single source of truth
FormData Coercion
Native (z.coerce)
String $\to$ Number/Date
Nested Error Paths
Automatic
error.flatten()
Inject:

📝 Live Form Input

Simulates user submission
⚡ Mode 3: Zod SafeParse Output
Type-Safe & Coerced
Zod 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
user.manual.ts
typescript
1// 1. You must maintain this interface manually:
2export interface ManualUser {
3 username: string;
4 email: string;
5 age: number;
6 // ... 8 more fields
7}
8
9// 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 assertion
24 return { success: Object.keys(errors).length === 0, data: input as ManualUser, errors };
25}
Zod Schema (~28 Lines) Single Source of Truth
user.schema.ts
typescript
1import { z } from 'zod';
2
3// 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});
18
19// 2. TypeScript type generated automatically (Zero Drift!):
20export type User = z.infer<typeof userSchema>;
21
22// 3. One-line safe execution:
23const result = userSchema.safeParse(formData);