Validating numeric query parameters in NestJS
Krzysztof Szala

Krzysztof Szala @avantar

About: PHP and TypeScript developer, currently working at DAZN. Don't want to miss my next article? Just click 👇

Location:
Silesia
Joined:
May 30, 2020

Validating numeric query parameters in NestJS

Publish Date: Apr 12 '21
88 17

Another day, another short article. Today we will focus on validation of numeric query parameters.

Everyone values his/her time, so here is TL;DR:

Each query parameter comes as string, so it's not possible to validate numeric params correctly. Simple solution – use @Type() decorator from class-transformer library, and declare numeric field as Number. I.e. @Type(() => Number)

Let's take an example URL address with few query parameters:

https://awesomesite.com/users?country=6&name=joe
Enter fullscreen mode Exit fullscreen mode

Our controller would probably look like this:

@Controller('users')
class UsersController{
  @Get()
  getUsers(@Query() queryParams){}
}
Enter fullscreen mode Exit fullscreen mode

Ok, at the beginning we've extracted our query parameters with @Query() decorator. Time to impose some validation on them. Here is our validation class:

class GetUsersQuery{
  @IsInt()
  country: number;

  @IsString()
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

We've defined very basic validation constraints for country and name fields. Then we need to do a little change in our controller method.

@Controller('users')
class UsersController{
  @Get()
  getUsers(@Query() queryParams: GetUsersQuery){
    console.log(queryParams)
  }
}
Enter fullscreen mode Exit fullscreen mode

Ok. Time to check if our validation works correctly. Let's try to send GET request for previously mentioned URL. Everything should be just fine, right? Well, not really, here is what we got:

{
    "statusCode": 400,
    "message": [
        "country must be a number conforming to the specified constraints"
    ],
    "error": "Bad Request"
}
Enter fullscreen mode Exit fullscreen mode

What? But country is anumeric field! It's even an integer! Unfortunately, not for our application. Let's try to make one step back, and remove validation, then check what kind of parameters will query object contain.

{ 
  country: '1',
  name: 'joe'
}
Enter fullscreen mode Exit fullscreen mode

Ok, now you can see, that each field is passed as a string value. Even an integer field. What can we do with it? After all, we need to validate whether the country field is an integer, or not, right?

Once again, class-transformer library has a simple solution for us. Use @Type decorator, and declare country field as Number:

class GetUsersQuery{
  @IsInt()
  @Type(() => Number)
  country: number;

  @IsString()
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

Now our request will pass validation, and the response object will look like this:

{ 
  country: 1,
  name: 'joe'
}
Enter fullscreen mode Exit fullscreen mode

Country field has numeric type now. When we send an invalid value, for example, a string, we'll get:

{
    "statusCode": 400,
    "message": [
        "country must be an integer number"
    ],
    "error": "Bad Request"
}
Enter fullscreen mode Exit fullscreen mode

But for integer type parameter it will pass. Finally, our integer validation works now correctly. Hope this short article will help you to validate numeric values passed through URL parameters. Cheers!

Comments 17 total

  • sebytab
    sebytabAug 13, 2021

    Just a little issue with this way of transform & validate query params: try @Type(() => Boolean), you'll have to use @Transform and do it yourself.

    • Krzysztof Szala
      Krzysztof SzalaAug 14, 2021

      Hi! Thank you for your comment. I'm not sure what do you want to achive with your code example. Could you give me an example of URL and final object with values you want to get? Regards!

      • sebytab
        sebytabAug 16, 2021

        Just try with some boolean param, or even an array.

        export class FilterQuery {
        @IsOptional()
        @IsBoolean()
        @Type(() => Boolean)
        prova?: boolean
        }

        Setting 'prova' as 'false' it will cast it to true. As you said, those query params are just strings that must be serialized first in some way. So, in some cases like Number it just works but in others it follows JS conversion rules... I partially solved it doing using @Transform instead of @Type and looking Nestjs parse implementations github.com/nestjs/nest/tree/99ee3f...

        • Krzysztof Szala
          Krzysztof SzalaAug 16, 2021

          You are totally right! And this is the reason why this article is titled “Validating numeric query params” 😁 I think it’s the most common issue Nest users encounter (at least in my experience).

          For array of values in query params I use (exactly as you said) Transform decorator from class-transform package. For bools I prefer to pass just boolean in numeric form (0 or 1) or (depends on context) simple string like yes/no. But I avoid true/false, because as you noted – it can result with some false positives.

          Anyway, thank you for your comment, I’m sure somebody finds it helpful! Cheers!

          • sebytab
            sebytabAug 17, 2021

            Thank you for the article, it's my first time with Nestjs and I was looking for how to parse & validate params declaratively and without too many hustles. I hadn't noticed "numeric" in the title, sorry 😅
            How do you manage arrays, i.e. numeric[] ? At the moment I use ParseArray (that also is the only Parse* function that can handle natively missing query params)

            • Krzysztof Szala
              Krzysztof SzalaAug 17, 2021

              I've struggled with arrays a lot, and ended up with some like this. I'm not very proud of it, but it works for me.

              I decorate expected field with:

              @IsArray()
              @Transform(({ value }) => (value ? Transformation.mapStringToNumber(value) : undefined))
              categories: number[];
              
              Enter fullscreen mode Exit fullscreen mode

              And here is mapStringToNumber implementation:

              export class Transformation {
                static mapStringToNumber(value: string | string[]): number[] | undefined {
                  const explodedValues = typeof value === 'string' ? value.split(',') : value;
                  const filteredValues = explodedValues.filter(Number).map(Number);
              
                  return filteredValues.length > 0 ? filteredValues : undefined;
                }
              }
              
              Enter fullscreen mode Exit fullscreen mode

              It allows me to pass array of numbers and just string with numbers separated with commas as well. It also cleans up non-numeric values. So, when you pass e.g. categories=1,a,2, you will get an array of [1,2].

              I'm not the biggest fan of using extra pipes for validating. I rather try to keep my validation in one place (in validation class with class-validator in this case).

  • ade suhada
    ade suhadaOct 5, 2021

    awesome, this is just working properly .. thanks buddy

  • AMINE BK
    AMINE BKNov 28, 2021

    It would be great if we could do something like :
    import IsEmail from 'class-validator'
    @Query('email', IsEmail) email:string

  • Bruce Xu
    Bruce XuJan 12, 2022

    If the example is not working on your code, please check if you enabled ValidationPipe. Find more here: docs.nestjs.com/techniques/validat...

    • Daniel Wong
      Daniel WongMar 9, 2022

      Furthermore, I discovered it is required to pass in the following options to ValidationPipe

          new ValidationPipe({
            transform: true,
            transformOptions: {
              enableImplicitConversion: true,
            },
          }),
      
      Enter fullscreen mode Exit fullscreen mode

      else the type of the query param value at runtime will still be string, even though it may pass the class-validator validations.

  • 度
    Jun 28, 2022

    Good article. I read all your articles on validation. And I have an ideas. I think if want to validate numeric,should use ParseIntPipe, it will transform and if it isn't a number it will throw error

    • Krzysztof Szala
      Krzysztof SzalaJul 1, 2022

      The main point of my approach is to have the whole validation process in one place :)

  • lvlei
    lvleiNov 15, 2022

    tk u gs

  • Nursultan Begaliev
    Nursultan BegalievMar 28, 2023

    Thank you)

  • EI Akoji
    EI AkojiAug 29, 2023

    Awesome article

Add comment