node.jstypescriptnestjsclass-validatorclass-transformer

Nest.js - DTO validation and transforming string to date


I'm expecting the coming request to be ISO 8601 standard timestampz, something like "2023-12-04T15:30:00Z" (naturally it comes as a string inside JSON, I'm trying to convert it to javascript Date)

Here's my DTO class:

export class CreateBookingDto implements ICreateBookingDto {
  @ApiProperty()
  @IsISO8601({ strict: true, strictSeparator: true })
  @Transform(() => Date)
  @IsNotEmpty()
  from_date: Date;

  @ApiProperty()
  @IsISO8601({ strict: true })
  @IsNotEmpty()
  @Type(() => Date)
  to_date: Date;
}

When I try to send a request, I receive this Bad Request Exception message: "from_date must be a valid ISO 8601 date string" (same for to_date)

the request:

{
    "from_date": "2023-12-04T15:30:00Z",
    "to_date": "2023-12-04T16:30:00Z"
}

I tried this too:

@Type(({ value }) => new Date(value))
to_date: Date;

but still the same issue

But when I change like this:

from_date: any;
// to_date also

It's accepting the request, I can change it to string to make it work, but I want Date.

So, my questions are:

  1. How can I correctly transform the coming "from_date" and "to_date" to Date type?
  2. Even when I set them to any, it accepts dates like "2023-12-04", I'm expecting only timestampz strictly, what is the proper way?

Solution

  • Transformations happen before validation, due to how class-validator needs a class instance to act on, and body-parser itself only deserializes the JSON/form-urlencoded body, but doesn't create a class of it (as JS is not necessarily a class based language). So Nest uses class-transformer inside the ValidationPipe to create the class instance that class-valdiator will then validate against using the DTO and the metadata from the decorators. This is why your @IsISO8601() is failing, because it's already a Date instance, you should use @IsDate() instead. If you want to preform some sort of validation before turning the string into a Date you can use the @Transform() decorator with the advanced approach from class-transformer's docs and do something like

    export class CreateBookingDto implements ICreateBookingDto {
      @ApiProperty()
      @IsISO8601({ strict: true, strictSeparator: true })
      @Transform(({ value }) => {
        const isValidDate = isISO8601(value, { strict: true, strictSeparator: true });
        if (!isValidDate) {
          throw new Error(`Property "from_date" should be a valid ISO8601 date string`);
        }
        return new Date(value);
      })
      @IsNotEmpty()
      from_date: Date;
    
      @ApiProperty()
      @IsISO8601({ strict: true })
      @IsNotEmpty()
      @Type(() => Date)
      to_date: Date;
    }
    

    It's not necessarily pretty, but it works, and you could save it to your own variable and make the decorator re-usable.