TypeScript / Zod / Walidacja

    Zod — zaawansowane wzorce

    Discriminated unions, branded types (nominal typing), transforms, refine, recursive schemas (z.lazy), React Hook Form, tRPC i env validation.

    .brand()
    Nominal typing
    .refine()
    Custom validation
    z.lazy()
    Recursive
    .transform()
    Transforms

    6 zaawansowanych wzorców Zod

    discriminatedUnion, brand, transform, refine, lazy i pipe — kategoria i zastosowanie w produkcyjnych aplikacjach TypeScript.

    Feature Kategoria Zastosowanie
    z.discriminatedUnion() Typy Union z discriminant key — lepsza wydajność niż z.union()
    .brand('UserId') Branded types Nominal typing — UserId nie jest string (TypeScript-only)
    .transform(fn) Transformacja Konwersja wartości — string to Date, trim, lowercase
    .refine(fn, msg) Custom validation Własna walidacja — async, cross-field (superRefine)
    z.lazy(() => schema) Recursive Cykliczne struktury — JSON, drzewo kategorii
    .pipe(schema) Kompozycja Chain schematów — coerce + validate w jednym

    Często zadawane pytania

    Co to jest Zod i dlaczego jest standardem walidacji w TypeScript?

    Zod: TypeScript-first schema validation. Runtime walidacja z pełnym type inference. Brak powtórzenia typów — jeden schema = typy + walidacja. Instalacja: npm install zod. Podstawy: import {z} from 'zod'. const UserSchema = z.object({name: z.string(), age: z.number().min(0).max(150), email: z.string().email(), role: z.enum(['admin', 'user', 'guest'])}). type User = z.infer(UserSchema). const result = UserSchema.safeParse(data). result.success: result.data lub result.error. Zalety nad ręcznymi typami: Walidacja runtime (np. dane z API, formularze). Type inference — nie piszesz typów ręcznie. Kompozycyjność schematów. Czytelne komunikaty błędów. Integracje: react-hook-form + zodResolver. Vercel AI SDK (generateObject). tRPC (input validation). Prisma (query params). Drizzle (insert schema). Zod vs Yup: Zod — TypeScript-first, lepszy type inference. Yup — starsza, mniej TypeScript-friendly. Zod vs io-ts: Zod — prostszy API. io-ts — czysto funkcyjny (fp-ts). Zod vs Valibot: Valibot — mniejszy bundle (3KB vs 13KB). Zod — bardziej popularny, więcej integracji. Parse nie validate: Zod.parse() rzuca przy błędzie. safeParse() bezpieczna wersja. parseAsync() dla async refine.

    Zaawansowane typy Zod — discriminated unions, branded types i transforms?

    Discriminated Union: const ShapeSchema = z.discriminatedUnion('type', [z.object({type: z.literal('circle'), radius: z.number()}), z.object({type: z.literal('rect'), width: z.number(), height: z.number()})]). Lepsza wydajność niż z.union() — sprawdza discriminant key. Branded Types (Nominal typing): const UserId = z.string().uuid().brand('UserId'). type UserId = z.infer(typeof UserId). function getUser(id: UserId) {...}. getUser('not-branded-string') — TypeScript błąd! Różne ID types nie są wymienne. Brand nie wpływa na runtime — tylko TypeScript. Transforms: z.string().transform(s => s.trim().toLowerCase()). z.string().transform(s => parseInt(s, 10)). z.preprocess(val => String(val), z.string()). TransformSchema.._input vs ._output — różne typy wejścia i wyjścia. Coerce: z.coerce.number() — konwertuje string do number. z.coerce.date() — konwertuje string do Date. Przydatne dla form data (wszystko string). Intersection: const A = z.object({a: z.string()}). const B = z.object({b: z.number()}). const AB = z.intersection(A, B). lub A.merge(B) — łącz obiekty. Partial i Required: UserSchema.partial() — wszystkie pola opcjonalne. UserSchema.required() — wszystkie wymagane. UserSchema.partial({name: true}) — tylko name opcjonalne. Pick i Omit: UserSchema.pick({name: true, email: true}). UserSchema.omit({password: true}).

    Refinements i custom validation w Zod?

    Refine — custom walidacja: z.string().refine(s => s.startsWith('https'), {message: 'Must start with https'}). z.number().refine(n => n % 2 === 0, 'Must be even'). Async refine: z.string().refine(async email => await checkEmailExists(email), 'Email already taken'). parseAsync() dla async refine. superRefine — wielokrotne błędy: z.object({password: z.string(), confirm: z.string()}).superRefine(({password, confirm}, ctx) => { if (password !== confirm) { ctx.addIssue({code: z.ZodIssueCode.custom, message: 'Passwords must match', path: ['confirm']}) } }). Walidacja warunkowa: z.discriminatedUnion lub z.union dla różnych kształtów danych. businessHours.superRefine oparty na dayOfWeek. Wzorzec: schema z refine per field. Custom error messages: z.string().min(8, {message: 'Hasło musi mieć min. 8 znaków'}). z.string().email('Nieprawidłowy email'). ZodError format: error.errors — tablica ZodIssue. issue.path — ścieżka do pola. issue.message — komunikat. issue.code — typ błędu. flatten(): const flat = error.flatten(). flat.fieldErrors — per field. flat.formErrors — globalne błędy. format(): const formatted = error.format(). nested errors. Custom ZodErrorMap: z.setErrorMap(customErrorMap). Globalne custom messages. Internacjonalizacja (i18n) błędów. Pipe: z.string().pipe(z.coerce.number()) — transform z walidacją.

    Zod z React Hook Form, tRPC i API validation?

    React Hook Form + Zod: npm install react-hook-form @hookform/resolvers. const schema = z.object({email: z.string().email(), password: z.string().min(8)}). const {register, handleSubmit, formState} = useForm({resolver: zodResolver(schema)}). formState.errors.email.message. Automatyczne type safety w form submit. TypeScript: SubmitHandler(z.infer(typeof schema)). tRPC + Zod input: publicProcedure.input(z.object({userId: z.string().uuid()})).query(async ({input}) => { return db.user.findUnique({where: {id: input.userId}}) }). Walidacja na poziomie procedury. Błędy walidacji jako TRPCError. API Route validation (Next.js): export async function POST(req: Request) { const body = await req.json(). const parsed = CreateUserSchema.safeParse(body). if (!parsed.success) { return Response.json({errors: parsed.error.flatten()}, {status: 400}) } const user = await createUser(parsed.data) }. Prisma + Zod: import {createInsertSchema, createSelectSchema} from 'drizzle-zod'. lub prisma-zod-generator. Automatyczne schema z modelu DB. Environment variables: import {createEnv} from '@t3-oss/env-nextjs'. env({server: {DATABASE_URL: z.string().url()}, client: {NEXT_PUBLIC_API_URL: z.string().url()}}). Walidacja env przy starcie. Nie zapomnisz o zmiennej env. URL search params: const paramsSchema = z.object({page: z.coerce.number().default(1), q: z.string().optional()}). paramsSchema.parse(Object.fromEntries(searchParams)).

    Zaawansowane wzorce Zod — lazy, recursive i schema composition?

    Recursive schemas (JSON): type Json = string | number | boolean | null | Json[] | {[key: string]: Json}. const JsonSchema: z.ZodType(Json) = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(JsonSchema), z.record(JsonSchema)])). lazy() dla cyklicznych referencji. Tree/nested struktura: const CategorySchema = z.object({id: z.number(), name: z.string(), children: z.array(z.lazy(() => CategorySchema)).optional()}). Record schema: z.record(z.string(), z.number()) — {[key: string]: number}. z.record(z.enum(['a', 'b']), z.string()) — tylko konkretne klucze. Map i Set: z.map(z.string(), z.number()). z.set(z.string()). Tuple: z.tuple([z.string(), z.number(), z.boolean()]) — fixed-length array. rest() dla dodatkowych elementów. Schema extension: const BaseSchema = z.object({id: z.string(), createdAt: z.date()}). const UserSchema = BaseSchema.extend({name: z.string(), email: z.string().email()}). extend() — nie mutuje oryginalnego. Schema registry pattern: Map(schemaKey => ZodSchema). Dynamiczna walidacja per type. Serializable schemas. ZodType utility: z.ZodType(T) — dla generic schemas. Infer nested: type UserWithPosts = z.infer(typeof UserWithPostsSchema). Default values: z.string().default('anonymous'). z.number().default(0). z.object().default({}) — parse pusty object. Catch: z.number().catch(0) — default przy błędzie zamiast throw. Optional vs Nullable vs default: optional() — undefined ok. nullable() — null ok. default() — zastąp undefined. nullish() — null i undefined ok.

    Czytaj dalej

    Powiązane artykuły

    Kontakt

    Skontaktuj się z nami

    Porozmawiajmy o Twoim projekcie. Bezpłatna wycena w ciągu 24 godzin.

    Wyślij zapytanie

    Bezpłatna wycena w 24h
    Bez zobowiązań
    Indywidualne podejście
    Ekspresowa realizacja

    Telefon

    +48 790 814 814

    Pon-Pt: 9:00 - 18:00

    Email

    adam@fotz.pl

    Odpowiadamy w ciągu 24h

    Adres

    Plac Wolności 16

    61-739 Poznań

    Godziny pracy

    Pon - Pt9:00 - 18:00
    Sob - NdzZamknięte

    Wolisz porozmawiać?

    Zadzwoń teraz i porozmawiaj z naszym specjalistą o Twoim projekcie.

    Zadzwoń teraz