always_bloo
always_bloo

Reputation: 185

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?

Upvotes: 6

Views: 8002

Answers (2)

asologor
asologor

Reputation: 707

As of 2022, NestJS docs say that it's possible to validate route params using the built-in validation pipe.

In a controller:

@Get(':id')
findOne(@Param() params: FindOneParams) {
  return 'This action returns a user';
}

Validation class:

import { IsNumberString } from 'class-validator';

export class FindOneParams {
  @IsNumberString()
  id: number;
}

Ref: https://docs.nestjs.com/techniques/validation#auto-validation

Upvotes: 13

Kim Kern
Kim Kern

Reputation: 60377

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

Upvotes: 4

Related Questions