Let's say I have following route:
companies/{companyId}/departments/{departmentId}/employees
Is it possible to validate both resources ids (companyId
, departmentId
) separately? I've tried following but it's not working.
class ResourceId {
@IsNumberString()
@StringNumberRange(...) // my custom validator
id: number;
}
@Get(':companyId/departments/:departmentId/employees')
getEmployees(
@Param('companyId') companyId: ResourceId,
@Param('departmentId') departmentId: ResourceId,
) {}
I have multiple cases when there is more than one parameter in the single route. I would not like to create separate validation class for every route. Is there a way to handle this problem in a different way?
The problem is that you have a plain string here. For the validation with class-validator
to work, you must instantiate a class, in your case ResourceId
. The built-in ValidationPipe
expects the value to be {id: '123'}
instead '123'
to be able to transform it automatically. But you can easily create your own validation pipe, that does this extra transformation.
export class ParamValidationPipe implements PipeTransform {
async transform(value, metadata: ArgumentMetadata) {
if (metadata.type === 'param') {
// This is the relevant part: value -> { id: value }
const valueInstance = plainToClass(metadata.metatype, { id: value });
const validationErrors = await validate(valueInstance);
if (validationErrors.length > 0) {
throw new BadRequestException(validationErrors, 'Invalid route param');
}
return valueInstance;
} else {
return value;
}
}
}
You can then use it on your controller:
@UsePipes(ParamValidationPipe)
@Get(':companyId/departments/:departmentId/employees')
getEmployees(
@Param('companyId') companyId: ResourceId,
@Param('departmentId') departmentId: ResourceId,
) {
return `id1: ${companyId.id}, id2: ${departmentId.id}`;
}