Documentation Index
Fetch the complete documentation index at: https://mintlify.com/BladimirGS/judicial-backend/llms.txt
Use this file to discover all available pages before exploring further.
Judicial Backend validates every incoming request body through a dedicated middleware layer built on class-validator and class-transformer. Each endpoint that accepts a body has a typed Data Transfer Object (DTO) whose fields are annotated with decorator-based constraints. Before the controller logic runs, the validateDto middleware deserializes the raw JSON into a DTO instance, validates it, and either forwards the cleaned object to the controller or immediately returns a structured 400 response listing every field that failed.
validateDto Middleware
validateDto is a generic factory that accepts a DTO class constructor and returns a standard Express middleware function.
export const validateDto = <T extends object>(dtoClass: ClassConstructor<T>) => {
return async (req: Request, res: Response, next: NextFunction) => {
const dto = plainToInstance(dtoClass, req.body);
const errors = await validate(dto, {
stopAtFirstError: true,
whitelist: true,
forbidNonWhitelisted: false,
});
if (errors.length > 0) {
const detalle = flattenErrors(errors);
return responseUtil.validationError(res, detalle);
}
req.body = dto;
next();
};
};
What each step does
| Step | Library call | Purpose |
|---|
| Deserialize | plainToInstance(dtoClass, req.body) | Converts the plain JSON object to a typed DTO instance so decorators are recognized. |
| Validate | validate(dto, { ... }) | Runs all class-validator constraints; returns an array of ValidationError objects. |
| Flatten | flattenErrors(errors) | Recursively extracts field names and the first constraint message into { campo, error } pairs. |
| Replace body | req.body = dto | Overwrites the raw body with the validated, whitelisted DTO so controllers receive a clean object. |
Validation options used:
stopAtFirstError: true — only one error per field is reported, keeping the response concise.
whitelist: true — undeclared properties are stripped from the DTO before it reaches the controller.
forbidNonWhitelisted: false — extra properties are silently removed rather than rejected.
Error flattening
The internal flattenErrors function recursively walks nested ValidationError trees (including arrays of nested objects) and produces a flat list:
const flattenErrors = (errors: ValidationError[]): { campo: string; error: string }[] => {
const result: { campo: string; error: string }[] = [];
for (const e of errors) {
if (e.constraints) {
result.push({
campo: e.property,
error: Object.values(e.constraints)[0],
});
}
if (e.children && e.children.length > 0) {
const isArrayField = e.children.every((child) => !isNaN(Number(child.property)));
if (isArrayField) {
for (const child of e.children) {
result.push(...flattenErrors(child.children ?? []));
}
} else {
result.push(...flattenErrors(e.children));
}
}
}
return result;
};
Applying validateDto on Routes
The middleware is inserted between the route declaration and the controller handler:
// auth.routes.ts
import { validateDto } from '../../core/middlewares/validation.middleware';
import { LoginDto } from './dtos/login.dto';
router.post('/login', validateDto(LoginDto), AuthController.login);
// apelacion.routes.ts
import { validateDto } from '../../core/middlewares/validation.middleware';
import { CreateApelacionDTO } from './dtos/apelacion/create-apelacion.dto';
router.post('/', validateDto(CreateApelacionDTO), ApelacionController.create);
If validation passes, req.body is replaced with the typed DTO instance and next() is called. If it fails, a 400 response is sent immediately and the controller is never invoked.
DTO Examples
LoginDto — simple required-field validation
import { IsString, IsNotEmpty } from 'class-validator';
export class LoginDto {
@IsString()
@IsNotEmpty({ message: 'El usuario es requerido' })
usuario!: string;
@IsString()
@IsNotEmpty({ message: 'La contraseña es requerida' })
contrasenia!: string;
}
CreateApelacionDTO — conditional validation with @ValidateIf
Many fields are only required when a specific materia (subject area) is selected. @ValidateIf lets the DTO express that logic declaratively without branching logic in the controller. The full DTO also composes three nested classes — CreateRelacionDTO, CreateParteDTO, and CreateDelitoRelacionDTO — which are validated recursively through @ValidateNested:
import { Type } from 'class-transformer';
import {
IsBoolean, IsNotEmpty, IsOptional, IsString,
IsInt, IsArray, ValidateIf, IsDateString,
ValidateNested, ArrayMinSize, MaxLength,
} from 'class-validator';
export class CreateDelitoRelacionDTO {
@IsInt({ message: 'El delito debe ser un número entero' })
@IsNotEmpty({ message: 'El id del delito es requerido' })
idDelito!: number;
}
export class CreateParteDTO {
@IsString({ message: 'El nombre debe ser texto' })
@IsNotEmpty({ message: 'El nombre es requerido' })
nombre!: string;
@IsOptional()
@IsString({ message: 'La dirección debe ser texto' })
direccion?: string;
@IsBoolean({ message: 'El campo menorEdad debe ser verdadero o falso' })
@IsNotEmpty({ message: 'El campo menorEdad es requerido' })
menorEdad!: boolean;
@IsInt({ message: 'El tipo de parte debe ser un número entero' })
@IsNotEmpty({ message: 'El tipo de parte es requerido' })
idTipoParte!: number;
@IsInt({ message: 'El campo sexo debe ser un número entero' })
@IsNotEmpty({ message: 'El campo sexo es requerido' })
idSexo!: number;
}
export class CreateRelacionDTO {
@IsNotEmpty({ message: 'El ofendido es requerido' })
@ValidateNested()
@Type(() => CreateParteDTO)
ofendido!: CreateParteDTO;
@IsNotEmpty({ message: 'El procesado es requerido' })
@ValidateNested()
@Type(() => CreateParteDTO)
procesado!: CreateParteDTO;
@IsArray({ message: 'Los delitos deben ser un arreglo' })
@ArrayMinSize(1, { message: 'Debe haber al menos un delito por relación' })
@ValidateNested({ each: true })
@Type(() => CreateDelitoRelacionDTO)
delitoRelaciones!: CreateDelitoRelacionDTO[];
}
export class CreateApelacionDTO {
@IsInt({ message: 'La materia debe ser un número entero' })
@IsNotEmpty({ message: 'La materia es requerida' })
idMateria!: number;
// Fields required only for materia penal (idMateria === 5)
@ValidateIf((o) => o.idMateria === 5)
@IsInt({ message: 'La apelación debe ser un número entero' })
@IsNotEmpty({ message: 'La apelación es requerida para materia penal' })
idApelacion?: number;
@ValidateIf((o) => o.idMateria === 5)
@IsInt({ message: 'El tipo de apelación debe ser un número entero' })
@IsNotEmpty({ message: 'El tipo de apelación es requerido para materia penal' })
idTipoApelacion?: number;
@ValidateIf((o) => o.idMateria === 5)
@IsDateString({}, { message: 'La fecha auto debe ser un formato YYYY-MM-DD' })
@IsNotEmpty({ message: 'La fecha auto es requerida' })
fechaAuto?: string;
@ValidateIf((o) => o.idMateria === 5)
@IsString({ message: 'El expediente/causa debe ser texto' })
@IsNotEmpty({ message: 'El expediente/causa es requerido' })
expedienteCausa?: string;
@ValidateIf((o) => o.idMateria === 5)
@IsInt({ message: 'El tipo de escrito debe ser un número entero' })
@IsNotEmpty({ message: 'El tipo de escrito es requerido para materia penal' })
idTipoEscrito?: number;
@ValidateIf((o) => o.idMateria === 5)
@IsInt({ message: 'El juzgado debe ser un número entero' })
@IsNotEmpty({ message: 'El juzgado es requerido para materia penal' })
idJuzgado?: number;
@ValidateIf((o) => o.idMateria === 5)
@IsOptional()
@IsString({ message: 'El folio de oficio debe ser texto' })
folioOficio?: string;
@ValidateIf((o) => o.idMateria === 5)
@IsOptional()
@IsString({ message: 'El expediente acumulado debe ser texto' })
@MaxLength(10, { message: 'El expediente acumulado no puede exceder los 10 caracteres' })
expedienteAcumulado?: string;
@ValidateIf((o) => o.idMateria === 5)
@IsOptional()
@IsInt({ message: 'Las fojas deben ser un número entero' })
fojas?: number;
@ValidateIf((o) => o.idMateria === 5)
@IsOptional()
@IsString({ message: 'Las observaciones deben ser texto' })
observaciones?: string;
// Fields required only for materia indígena (idMateria === 6)
@ValidateIf((o) => o.idMateria === 6)
@IsInt({ message: 'El municipio debe ser un número entero' })
@IsNotEmpty({ message: 'El municipio es requerido para materia indígena' })
idMunicipio?: number;
@ValidateIf((o) => o.idMateria === 6)
@IsInt({ message: 'La localidad debe ser un número entero' })
@IsNotEmpty({ message: 'La localidad es requerida para materia indígena' })
idLocalidad?: number;
@ValidateIf((o) => o.idMateria === 6)
@IsInt({ message: 'La etnia debe ser un número entero' })
@IsNotEmpty({ message: 'La etnia es requerida para materia indígena' })
idEtnia?: number;
@ValidateIf((o) => o.idMateria === 6)
@ValidateIf((o) => o.idEtnia === 17)
@IsString({ message: 'El campo otroEtnia debe ser texto' })
@IsNotEmpty({ message: 'La otroEtnia es requerida para materia indígena' })
otroEtnia?: string;
@ValidateIf((o) => o.idMateria === 6)
@IsString({ message: 'El lugar de hechos debe ser texto' })
@IsNotEmpty({ message: 'El lugar de hechos es requerida para materia indígena' })
lugarHechos?: string;
@ValidateIf((o) => o.idMateria === 6)
@IsOptional()
@IsString({ message: 'El asunto debe ser texto' })
asunto?: string;
// Common fields
@IsOptional()
@IsBoolean({ message: 'El campo esReposicion debe ser verdadero o falso' })
esReposicion!: boolean;
@IsOptional()
@IsInt({ message: 'El Magistrado debe ser un número entero' })
idMagistrado!: number;
@IsArray({ message: 'Las relaciones deben ser un arreglo' })
@ArrayMinSize(1, { message: 'Debe haber al menos una relación' })
@ValidateNested({ each: true })
@Type(() => CreateRelacionDTO)
relaciones!: CreateRelacionDTO[];
}
@ValidateNested({ each: true }) combined with @Type(() => NestedClass) is required for class-transformer to correctly instantiate nested objects so their own decorators are also applied during validation.
Query Parameter Validation
Not all validation flows through validateDto. Some endpoints validate query parameters imperatively inside the controller. For example, GET /api/apelaciones/catalogos checks that the materia query parameter is strictly one of the two accepted string values:
export const ApelacionController = {
getCatalogos: asyncHandler(async (req: Request, res: Response) => {
const { materia } = req.query;
if (materia !== 'penal' && materia !== 'indigena') {
return responseUtil.validationError(res, {
materia: 'El parámetro materia es inválido. Debe ser "penal" o "indigena".',
});
}
const data = await ApelacionService.getCatalogos(String(materia));
return responseUtil.success(res, data, 'Catálogos cargados correctamente');
}),
};
This pattern is used for simple enum-like checks where creating a full DTO class would be disproportionate.
Validation Error Response Shape
When validation fails — whether from validateDto or an inline responseUtil.validationError call — the client receives:
{
"status": "error",
"code": "VALIDATION_ERROR",
"message": "Los datos enviados son inválidos",
"errors": [
{ "campo": "usuario", "error": "El usuario es requerido" },
{ "campo": "contrasenia", "error": "La contraseña es requerida" }
]
}
Each entry in errors has:
| Field | Type | Description |
|---|
campo | string | The DTO property name that failed validation. |
error | string | The first constraint message for that property. |