# 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/<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/<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)
