Skip to main content

Concern

Schema and runtime validation (forms, API boundaries).

Technology

zod

Version

v4

Documentation

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() — use z.enum(MyEnum) or z.enum(['a', 'b']). Avoid ColorSchema.Enum.Red / ColorSchema.Values.Red — use ColorSchema.enum.Red.
  • Error customization: Avoid message — use error. Avoid invalid_type_error / required_error — use error with a function. Avoid errorMap — use error.
  • ZodError: Avoid err.format() and err.flatten() — use top-level z.treeifyError(err). Avoid err.errors — use err.issues. Avoid err.formErrors — removed; use z.treeifyError() or issues.
  • Strings and formats: Avoid z.string().email(), z.string().uuid(), z.string().url() — use top-level z.email(), z.uuid(), z.url(). Avoid z.string().ip() — use z.ipv4() or z.ipv6(). Avoid z.string().cidr() — use z.cidrv4() or z.cidrv6().
  • Objects: Avoid .strict() / .passthrough() on objects — use z.strictObject({ ... }) or z.looseObject({ ... }). Avoid .merge() — use .extend(OtherSchema.shape) or object spread. Avoid .deepPartial() — removed in v4.
  • Records and other: Avoid z.record(valueSchema) (single argument) — use z.record(keySchema, valueSchema). Avoid .refine(fn, (val) => ({ message: "..." })) — use .refine(fn, { error: "..." }). Avoid z.promise(schema); parse the awaited value instead. Avoid z.ostring(), z.onumber() etc. — use z.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).