Search code examples
javascripttypescriptnestjsclass-validatorclass-transformer

Using Nest JS Validation Pipe and class-transformer to get kebab-case query params


I'm trying to make use of Nest JS Validation Pipe to auto transform and validate my GET Request Query Params

e.g

{{url}}/path?param-one=value&param-two=value

On app.module.ts, I have the following code to set global validation pipe

    app.useGlobalPipes(
      new ValidationPipe({
        transform: true,
        whitelist: true,
        forbidNonWhitelisted: true,
      }),
    );

And I have a DTO to do the validation

class MyValidationDto {
   @IsString()
   paramOne: string

   @IsString()
   paramTwo: string
}

And in my controller, I make use of the MyValidationDto class

class MyController {
   ... stuff

   @Get('/path')
   async myFunction (Query() queryParams: MyValidationDto) { ...code }
}

However, I'm not sure where to go to in order to parse the kebab case query keys param-one and param-two to the camelCase class properties paramOne and paramTwo in the validation DTO

I've tried looking at Nest JS doc, class-validator doc and class-transformer doc, as well as search the highs and lows of internet to no luck. This should be a fairly common case so not sure where I'm going wrong here

Unless this is not possible and I should be using the Query() decorator instead. Please advise :pray:


Solution

  • TLDR; There's no native method from NestJS or class-validator to do this for now (a similar feature request is still open on CT repo).

    However, there are a couple of workarounds:

    You can use @Expose() but you still need to customize your error message as the new exposed name won't be passed to the context:

    export class SomeDTO {
      @IsString()
      @Expose({ name: 'my-name' })
      myName: string;
      ..
    

    Or you can create a NestJS pipe that normalizes your query params before they hit your controller.

    For example, create a new pipe:

    @Injectable()
    export class NormalizeQueryParamsPipe implements PipeTransform {
      transform(value: RestaurantDTO, metadata: ArgumentMetadata) {
        const normalizedQueryParams = {
          ...value,
          name: value['my-name'],
        };
    
        delete normalizedQueryParams['my-name'];
        return normalizedQueryParams;
      }
    }
    

    Of course, you may do better than renaming attributes manually and using an approach like this combined with this: Basically use class validator methods combined with a transformer that converts your kebab-case keys to camelcase (e.g plainToClass(camelcaseKeys(value), MyValidationDto))

    This transforms your 'my-name' to myName, and you want to have this before the global validation pipe:

    // in main.ts:
    
    app.useGlobalPipes(new NormalizeQueryParamsPipe());
    app.useGlobalPipes(new ValidationPipe());
    
    // or: app.useGlobalPipes(new NormalizeQueryParamsPipe(), new ValidationPipe());
    

    In case you are not using a global validation pipe, you can just use @UsePipes() in your controller:

    @Get()
    @UsePipes(new NormalizeQueryParamsPipe())
    async getAll() {
     ..