# Zod Schema Validation: Type-Safe Validation für TypeScript **Meta-Description:** Umfassender Guide zu Zod für Schema-Validierung. TypeScript-Integration, API-Validation, Form-Handling und Best Practices für robuste Anwendungen. **Keywords:** Zod, Schema Validation, TypeScript Validation, Form Validation, API Validation, Runtime Validation, Type Safety --- ## Einführung Zod ist die führende Schema-Validierungsbibliothek für TypeScript. Sie bietet **Runtime-Validierung mit automatischer Type-Inferenz** – eine perfekte Brücke zwischen TypeScript's Compile-Time-Checks und der Realität von externen Daten. --- ## Das Problem ```typescript // TypeScript prüft nur zur Compile-Time interface User { name: string; email: string; age: number; } // Aber was passiert zur Runtime? const userData = await fetch('/api/user').then(r => r.json()); // userData könnte ALLES sein - TypeScript vertraut blind const user: User = userData; // Keine Runtime-Prüfung! ``` --- ## Die Lösung: Zod ```typescript import { z } from 'zod'; // Schema definieren const UserSchema = z.object({ name: z.string().min(2), email: z.string().email(), age: z.number().int().positive() }); // Type automatisch inferieren type User = z.infer; // { name: string; email: string; age: number } // Runtime-Validierung const userData = await fetch('/api/user').then(r => r.json()); const user = UserSchema.parse(userData); // Wirft bei ungültigen Daten! // Oder mit safeParse für Error-Handling const result = UserSchema.safeParse(userData); if (result.success) { console.log(result.data); // Typisiert als User } else { console.error(result.error.errors); } ``` --- ## Basis-Typen ```typescript import { z } from 'zod'; // Primitive Typen const stringSchema = z.string(); const numberSchema = z.number(); const booleanSchema = z.boolean(); const dateSchema = z.date(); const bigintSchema = z.bigint(); const symbolSchema = z.symbol(); const undefinedSchema = z.undefined(); const nullSchema = z.null(); const voidSchema = z.void(); const anySchema = z.any(); const unknownSchema = z.unknown(); const neverSchema = z.never(); // Literale const tuna = z.literal('tuna'); const twelve = z.literal(12); const isTrue = z.literal(true); // Enums const FishEnum = z.enum(['Salmon', 'Tuna', 'Trout']); type FishEnum = z.infer; // 'Salmon' | 'Tuna' | 'Trout' // Native Enums enum Fruits { Apple, Banana } const FruitEnum = z.nativeEnum(Fruits); ``` --- ## String Validierung ```typescript const stringSchema = z.string() // Länge .min(5, 'Mindestens 5 Zeichen') .max(100, 'Maximal 100 Zeichen') .length(10, 'Exakt 10 Zeichen') // Format .email('Ungültige E-Mail') .url('Ungültige URL') .uuid('Ungültige UUID') .cuid('Ungültige CUID') .datetime('Ungültiges Datum') .ip('Ungültige IP') // Regex .regex(/^[a-z]+$/, 'Nur Kleinbuchstaben') // Transformationen .trim() .toLowerCase() .toUpperCase() // Custom .refine(val => val.includes('@'), 'Muss @ enthalten'); // Praktisches Beispiel const UsernameSchema = z.string() .min(3, 'Username zu kurz') .max(20, 'Username zu lang') .regex(/^[a-zA-Z0-9_]+$/, 'Nur Buchstaben, Zahlen und _') .toLowerCase(); ``` --- ## Number Validierung ```typescript const numberSchema = z.number() // Constraints .gt(0, 'Größer als 0') .gte(0, 'Größer oder gleich 0') .lt(100, 'Kleiner als 100') .lte(100, 'Kleiner oder gleich 100') .positive('Muss positiv sein') .negative('Muss negativ sein') .nonpositive() .nonnegative() .multipleOf(5, 'Muss durch 5 teilbar sein') .int('Muss Ganzzahl sein') .finite() .safe(); // JavaScript safe integer // Praktisches Beispiel: Preis const PriceSchema = z.number() .positive('Preis muss positiv sein') .multipleOf(0.01, 'Maximal 2 Dezimalstellen') .max(1000000, 'Preis zu hoch'); ``` --- ## Object Schemas ```typescript // Basis Object const UserSchema = z.object({ name: z.string(), email: z.string().email(), age: z.number().int().positive() }); // Optional & Default const UserWithDefaultsSchema = z.object({ name: z.string(), email: z.string().email(), age: z.number().optional(), // number | undefined role: z.string().default('user'), // Hat immer einen Wert isActive: z.boolean().nullable() // boolean | null }); // Extend const AdminSchema = UserSchema.extend({ permissions: z.array(z.string()) }); // Pick & Omit const UserNameOnly = UserSchema.pick({ name: true }); const UserWithoutAge = UserSchema.omit({ age: true }); // Partial & Required const PartialUser = UserSchema.partial(); // Alle optional const RequiredUser = PartialUser.required(); // Alle required // Strict Mode const StrictUser = UserSchema.strict(); // Wirft bei extra Keys // Passthrough & Strip const PassthroughUser = UserSchema.passthrough(); // Behält extra Keys const StrippedUser = UserSchema.strip(); // Entfernt extra Keys (default) ``` --- ## Array & Tuple ```typescript // Array const StringArraySchema = z.array(z.string()); const NumberArraySchema = z.array(z.number()) .min(1, 'Mindestens 1 Element') .max(10, 'Maximal 10 Elemente') .nonempty('Darf nicht leer sein'); // Tuple const CoordinateSchema = z.tuple([ z.number(), // x z.number(), // y z.number().optional() // z (optional) ]); type Coordinate = z.infer; // [number, number, number?] // Rest Elements const StringsThenNumbers = z.tuple([z.string(), z.string()]) .rest(z.number()); // [string, string, ...number[]] ``` --- ## Union & Discriminated Union ```typescript // Union const StringOrNumber = z.union([z.string(), z.number()]); // Shorthand const StringOrNull = z.string().nullable(); // string | null const StringOrUndefined = z.string().optional(); // string | undefined // Discriminated Union (performanter) const ResultSchema = z.discriminatedUnion('status', [ z.object({ status: z.literal('success'), data: z.string() }), z.object({ status: z.literal('error'), error: z.string() }) ]); type Result = z.infer; // { status: 'success'; data: string } | { status: 'error'; error: string } ``` --- ## Transformationen ```typescript // Transform Output const NumberFromString = z.string().transform(val => parseInt(val, 10)); // Input: string, Output: number // Preprocessing const NumberSchema = z.preprocess( (val) => { if (typeof val === 'string') return parseInt(val, 10); return val; }, z.number() ); // Praktisches Beispiel: API Response const ApiResponseSchema = z.object({ created_at: z.string().transform(val => new Date(val)), price_cents: z.number().transform(val => val / 100), is_active: z.union([z.boolean(), z.literal('true'), z.literal('false')]) .transform(val => val === true || val === 'true') }); // Input: { created_at: "2024-01-15", price_cents: 1999, is_active: "true" } // Output: { created_at: Date, price_cents: 19.99, is_active: true } ``` --- ## Custom Validation mit Refine ```typescript // Einfaches Refine const PasswordSchema = z.string() .min(8) .refine( (val) => /[A-Z]/.test(val), 'Muss Großbuchstaben enthalten' ) .refine( (val) => /[0-9]/.test(val), 'Muss Zahlen enthalten' ); // Superrefine für komplexe Logik const SignupSchema = z.object({ password: z.string().min(8), confirmPassword: z.string() }).superRefine((data, ctx) => { if (data.password !== data.confirmPassword) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Passwörter stimmen nicht überein', path: ['confirmPassword'] }); } }); // Async Validation const UniqueEmailSchema = z.string().email().refine( async (email) => { const exists = await checkEmailExists(email); return !exists; }, 'E-Mail bereits vergeben' ); ``` --- ## Integration mit React Hook Form ```typescript import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; const SignupSchema = z.object({ name: z.string().min(2, 'Name zu kurz'), email: z.string().email('Ungültige E-Mail'), password: z.string().min(8, 'Mindestens 8 Zeichen') }); type SignupData = z.infer; function SignupForm() { const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(SignupSchema) }); const onSubmit = (data: SignupData) => { console.log(data); // Typsicher! }; return (
{errors.name && {errors.name.message}} {errors.email && {errors.email.message}} {errors.password && {errors.password.message}}
); } ``` --- ## API Route Validation (Next.js) ```typescript // app/api/users/route.ts import { z } from 'zod'; import { NextRequest, NextResponse } from 'next/server'; const CreateUserSchema = z.object({ name: z.string().min(2), email: z.string().email(), role: z.enum(['admin', 'user']).default('user') }); export async function POST(request: NextRequest) { try { const body = await request.json(); const data = CreateUserSchema.parse(body); // data ist typsicher const user = await createUser(data); return NextResponse.json(user, { status: 201 }); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( { errors: error.errors }, { status: 400 } ); } throw error; } } ``` --- ## Error Handling ```typescript import { z } from 'zod'; const UserSchema = z.object({ name: z.string().min(2), email: z.string().email() }); try { UserSchema.parse({ name: 'A', email: 'invalid' }); } catch (error) { if (error instanceof z.ZodError) { // Formatierte Errors console.log(error.format()); /* { name: { _errors: ['String must contain at least 2 character(s)'] }, email: { _errors: ['Invalid email'] } } */ // Flache Error-Liste console.log(error.flatten()); /* { formErrors: [], fieldErrors: { name: ['String must contain at least 2 character(s)'], email: ['Invalid email'] } } */ } } ``` --- ## Fazit Zod bietet: 1. **Runtime + Compile-Time Safety**: Validierung wo TypeScript aufhört 2. **Type Inference**: Keine doppelten Definitionen 3. **Composability**: Schemas kombinieren und erweitern 4. **Framework-Agnostisch**: React, Vue, Node.js, etc. Für jede Anwendung mit externen Daten ist Zod unverzichtbar. --- ## Bildprompts 1. "Shield protecting data, validation concept, type-safe illustration" 2. "TypeScript and runtime validation merging, code security concept" 3. "Form with checkmarks appearing as user types, validation feedback visualization" --- ## Quellen - [Zod Documentation](https://zod.dev/) - [Zod GitHub](https://github.com/colinhacks/zod) - [React Hook Form + Zod](https://react-hook-form.com/get-started#SchemaValidation) - [tRPC + Zod Integration](https://trpc.io/docs/server/validators)