Files
web_sport/docs/adr/0006-zod-schemas-shared-between-api-and-frontends.md
T

1.5 KiB

ADR-0006: Zod schemas shared between API and frontends

  • Status: Accepted
  • Date: 2026-08-11

Context

Validation rules exist twice by default: once in the API and once in the form. They drift, and the drift shows up as a form that accepts input the server rejects.

Decision

One Zod schema per input shape, defined in @sport/validation and imported by both sides. The API applies it through ZodValidationPipe; the frontends apply the same object to their forms. Zod was chosen over class-validator specifically because a class with decorators cannot cross into a React form, whereas a schema object can.

Scope is deliberately limited to shape and format rules. Anything requiring database state — "is this coupon still valid", "is this variant in stock" — is a business rule and lives in the backend service layer.

Consequences

A rule change happens once. Field-level errors come back keyed by dotted path (items.0.quantity), which forms consume directly. The parsed output carries coercions and defaults, so controllers receive clean typed data.

The discipline required is keeping business rules out of the schemas; a validation package that starts querying is a validation package that can no longer be shared.

Alternatives considered

class-validator + class-transformer, the NestJS default — rejected: not shareable with the frontends. Duplicating rules with a test to keep them in sync — rejected: the test tells you about drift after it has already shipped.