Search code examples
javascriptnode.jstypescriptnestjsclass-validator

Is it possible to validate single route parameter?


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?


Solution

  • 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}`;
    }