Appearance
API Zod Request Validation
Objective
Use shared schemas from @repo/schemas as the single source of truth for both request validation and request types.
Canonical Pattern
Import the schema and inferred type directly, then validate at @Body():
ts
import {
registerUserSchema,
type RegisterUserDto,
} from '@repo/schemas/identity';
import { ZodValidationPipe } from '@api/shared/infrastructure/pipes/zod-validation.pipe';
@Post('register')
async register(
@Body(new ZodValidationPipe(registerUserSchema)) dto: RegisterUserDto,
): Promise<{ message: string }> {
// ...
}The same pipe applies to @Query() when the whole query object should be validated — for example GET /agency/id uses getAgencyIdQuerySchema from @repo/schemas/identity for ?slug= / ?hostname= mutual exclusivity and hostname shape.
Rules
- Do not create class DTO wrappers only for validation metadata.
- Do not duplicate DTO interfaces in
apps/apiwhen the type already exists in@repo/schemas. - Prefer endpoint-explicit validation with
@Body(new ZodValidationPipe(schema)). - Keep schemas and inferred DTO types in
packages/schemasand consume them from API controllers/handlers.
Error Behavior
- Invalid payloads return
400 Bad Request. - The response detail comes from Zod issues produced by
safeParse.
Migration Notes
- Legacy
@ZodSchemaclass-decorator validation is removed. - Existing endpoints should be migrated by replacing:
- local class DTO import
@Body() dto: LocalDto
- With:
schema+typeimports from@repo/schemas/...@Body(new ZodValidationPipe(schema)) dto: SharedDtoType