Request pattern: frontend to backend
All API requests go through the same client that expects the unified response format (see responses). Request bodies are serialized per serialization (use instanceToPlain when the body is a class instance; the shared API client does this for post/put/patch). Optionally validate with zod on the client for forms (see forms).
Backend: request handling and validation
Use NestJS pipes for transformation and validation at the system boundary. Prefer built-in pipes where they fit; use schema-based validation with Zod for request bodies (and optionally query/params) via a custom ZodValidationPipe (included in the boilerplate).
Built-in pipes (Parse*Pipe, DefaultValuePipe)
Use Nest’s built-in pipes for params, query, and primitive/typed values:
ParseIntPipe,ParseFloatPipe,ParseBoolPipe— transform and validate numeric/boolean params or query values. Bind at parameter level:@Param('id', ParseIntPipe) id: number,@Query('count', new ParseIntPipe({ optional: true })) count?: number.ParseUUIDPipe,ParseEnumPipe,ParseArrayPipe,ParseDatePipe— for UUIDs, enums, arrays, dates. See Built-in pipes.DefaultValuePipe— provide defaults when a query or param is optional:@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number.
These pipes throw Nest’s standard exceptions; the global exception filter (see responses) maps them to the shared client-error envelope.
Object / schema validation: ZodValidationPipe
For request body (and optionally query objects), use schema-based validation with Zod via the custom ZodValidationPipe (Object schema validation):
- Constructor: Accept a Zod schema so the pipe is reusable per route with a different schema.
- Transform: Call
schema.safeParse(value). If success, return the parsed value. If failure, throw the Zod error (ZodError). An exception filter catches it and maps to the shared client-error format (seeresponses). - Binding for
@Body: Either method-level with@UsePipes(new ZodValidationPipe(bodySchema))so the next@Body()parameter is validated, or parameter-level with@Body(new ZodValidationPipe(bodySchema)) createDto: CreateDto. Both ensure the handler receives validated, typed input. - Example (method-level):
@Post() @UsePipes(new ZodValidationPipe(createCatSchema)) async create(@Body() createCatDto: CreateCatDto) { ... }. The schema is passed once per handler;@Body()receives the pipe-validated body. - Example (parameter-level):
@Post() async create(@Body(new ZodValidationPipe(createCatSchema)) createCatDto: CreateCatDto) { ... }. The pipe is bound directly to the body parameter.
Define a Zod schema (e.g. createSomethingSchema) and derive the DTO type with z.infer<typeof createSomethingSchema>. Zod typically requires strictNullChecks in tsconfig.json.
Pipe contract (NestJS)
Pipes implement PipeTransform (transform(value, metadata: ArgumentMetadata)). metadata has type: 'body' | 'query' | 'param' | 'custom', metatype, and optional data. Pipes run in the exceptions zone: when a pipe throws, the exception filter handles it and the route handler is not executed. See Pipes.
Implementation
Backend: The boilerplate adds ZodValidationPipe in backend/src/pipes/. You pass a Zod schema to the pipe (per route or per parameter); it runs schema.safeParse() on the body (or query) and either returns the parsed value or throws the ZodError. That error is caught by the global ZodExceptionFilter in backend/src/filters/, which maps it to the shared client-error envelope (including fieldErrors for per-field validation messages). The same app module registers HttpExceptionFilter and AllExceptionsFilter, so all errors end up in the unified shape (see responses). Use body validation by binding the pipe: @UsePipes(new ZodValidationPipe(mySchema)) at method level or @Body(new ZodValidationPipe(mySchema)) at parameter level.
Frontend: All API calls go through handleRequest<T> in frontends/shared/src/lib/api.ts, which returns Promise<ApiResponse<T>> and never throws on 4xx/5xx—so request/response handling is aligned with the backend envelope and filters.
Frontend: client and serialization
Use handleRequest<T> (wrapper around axios) for all API requests so every call returns the unified envelope type ApiResponse<T>. Serialize request bodies per serialization (e.g. instanceToPlain or shapes aligned with zod). When sending form data, align with forms and the same schemas used by the backend validation pipe so fieldErrors from the response can be applied to form fields (e.g. setError).
References
- NestJS: Pipes (PipeTransform, binding with
@UsePipes, parameter/metadata, global pipes). - Response contract and envelope:
responses. - Backend:
controller-layer,validation(zod),framework. - Frontend:
http-client,serialization,forms; link toresponsesfor the response contract.