DTO & Error Normalization
Strict validation pipe and one consistent error shape end to end
Status
NestJS class-validator DTOs are the idiomatic approach and coverage is good. The remaining gaps — strict ValidationPipe, typed DTO properties, one error shape — are tracked in plan phase 4 (Security §5) and Backend quality §2.
The cal.diy pattern
tRPC procedures use input: z.object({ ... }) Zod schemas directly on the router — the same schema validates the HTTP body and generates the TypeScript input type. Validation errors return a typed TRPCClientError with field-level details.
The REST equivalent of that guarantee is: strict pipe + typed DTOs + a normalized error shape the frontend can parse.
What to implement
1. Strict global ValidationPipe
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // strip unknown fields
forbidNonWhitelisted: true, // throw 400 if unknown fields present
transform: true, // auto-transform payloads to DTO class instances
transformOptions: { enableImplicitConversion: true },
})
);(Same change as Security §5 — one PR.)
2. Normalize error shapes with a global ExceptionFilter
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from "@nestjs/common";
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const status = exception.getStatus();
const body = exception.getResponse() as Record<string, unknown>;
response.status(status).json({
statusCode: status,
message: Array.isArray(body.message) ? "Validation failed" : body.message,
errors: Array.isArray(body.message)
? (body.message as string[]).map((m) => ({ message: m }))
: undefined,
});
}
}Register with app.useGlobalFilters(new HttpExceptionFilter()). The frontend half (parsing errors[] into react-hook-form field errors) is in API layer §3.
3. No any in DTOs
Typed, enum-based DTO properties are specified in Backend quality §2. CI gate once clean:
- name: No any in DTOs
run: |
! grep -rn ": any\|as any" apps/back/src/modules/**/dto/ --include="*.ts"