Concern
Schema and runtime validation (forms, API boundaries).
Technology
zod
Version
v4
Documentation
- Website: https://zod.dev/
- Repository: https://github.com/colinhacks/zod
- Getting started: https://zod.dev/?id=installation
Implementation
Zod is used in two places. In the backend, it is a dependency and powers the custom ZodValidationPipe (see pattern requests): request bodies are validated with a Zod schema and failures produce a ZodError that the exception filter maps to the shared client-error envelope with fieldErrors. In the shared package, shared/src/api-response.ts defines the zod schema fieldErrorsSchema and exports the TypeScript types for the response envelope (ApiResponse<T>, SuccessResponse<T>, ClientErrorResponse, ServerErrorResponse) so backend and frontends share the same contract. The pipe and the shared types keep validation and response shapes aligned.
Integration
Avoid Zod v3 patterns (deprecated or removed in v4)
Use v4 APIs so schemas and error handling stay valid as v3 APIs are deprecated or removed. See the Zod v4 migration guide for the full list.
- Enums: Avoid
z.nativeEnum()— usez.enum(MyEnum)orz.enum(['a', 'b']). AvoidColorSchema.Enum.Red/ColorSchema.Values.Red— useColorSchema.enum.Red. - Error customization: Avoid
message— useerror. Avoidinvalid_type_error/required_error— useerrorwith a function. AvoiderrorMap— useerror. - ZodError: Avoid
err.format()anderr.flatten()— use top-levelz.treeifyError(err). Avoiderr.errors— useerr.issues. Avoiderr.formErrors— removed; usez.treeifyError()orissues. - Strings and formats: Avoid
z.string().email(),z.string().uuid(),z.string().url()— use top-levelz.email(),z.uuid(),z.url(). Avoidz.string().ip()— usez.ipv4()orz.ipv6(). Avoidz.string().cidr()— usez.cidrv4()orz.cidrv6(). - Objects: Avoid
.strict()/.passthrough()on objects — usez.strictObject({ ... })orz.looseObject({ ... }). Avoid.merge()— use.extend(OtherSchema.shape)or object spread. Avoid.deepPartial()— removed in v4. - Records and other: Avoid
z.record(valueSchema)(single argument) — usez.record(keySchema, valueSchema). Avoid.refine(fn, (val) => ({ message: "..." }))— use.refine(fn, { error: "..." }). Avoidz.promise(schema); parse the awaited value instead. Avoidz.ostring(),z.onumber()etc. — usez.string().optional()(or equivalent).
Enums (Zod v4)
Use z.enum() for enum validation: z.enum(MyEnum) for TypeScript enums, or z.enum(['active', 'pending_review']) for string literal unions. z.nativeEnum() is deprecated in Zod v4; the unified z.enum() API replaces it. Enum string values (the underlying value, e.g. the 'active' in Status.Active = 'active') must be in snake_case (see pattern models).
form-handling / react-hook-form
Form validation via @hookform/resolvers with react-hook-form (see form-handling). Shared DTOs/schemas can align with backend (shared package).