KwadMarket Docs
Best Practices

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

apps/back/src/main.ts
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

apps/back/src/common/filters/http-exception.filter.ts
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"

On this page