# BFF Validation Standard (2025): DTOs + Global Pipe (Zod) This repository follows the “big org standard” for NestJS request validation: - **Schemas live in the shared domain layer** (`@customer-portal/domain`) - **Controllers use DTOs** built from those schemas - **Validation runs globally** via a single app-wide pipe --- ## Standard Pattern ### 1) Define the schema in `@customer-portal/domain` Put request/param/query schemas in the relevant domain module’s `schema.ts`. Example (conceptual): ```ts // packages/domain//schema.ts import { z } from "zod"; export const exampleRequestSchema = z.object({ name: z.string().min(1), }); ``` ### 2) Create a DTO in the controller using `createZodDto(schema)` ```ts import { createZodDto } from "nestjs-zod"; import { exampleRequestSchema } from "@customer-portal/domain/"; class ExampleRequestDto extends createZodDto(exampleRequestSchema) {} ``` Then use `ExampleRequestDto` in `@Body()`, `@Param()`, or `@Query()`. ### 3) Rely on the global `ZodValidationPipe` The BFF registers `ZodValidationPipe` globally via `APP_PIPE` in `apps/bff/src/app.module.ts`. That means controllers should **not** import `zod` or define ad-hoc Zod schemas inline for request validation. --- ## Boundary Validation (Integrations / Mapping) In addition to request DTO validation, we validate at integration boundaries: - **Provider raw → domain**: validate raw payloads and the mapped domain model using domain schemas. - **BFF → Portal**: use the same domain contracts for stable payload shapes where possible. --- ## Governance / Linting We enforce this pattern via ESLint: - Controllers are expected to import schemas from `@customer-portal/domain` - Controllers should not import `zod` directly (to prevent drifting schema definitions into the controller layer)