DTO data verification

  1. DTO

    DTO is short for data transfer object. A DTO is an object that encapsulates data and sends it from one application to another. DTOs help us define interfaces or inputs and outputs within the system.

    That is, the DTO defines the format of the data to be received. Similar to schema (but only validates the incoming data part). The objectId is automatically generated by the database and does not need to be verified.

    DTOs are just simple objects, they don’t contain any business logic, methods, or anything that needs to be tested.


  2. Generate DTO file

    Generated location: under the dto folder of the same level as coffees.controller.ts.

    1
    2
    3
    4
    5
    6
    7
    8
    |-- src
    |-- coffees module folder
    |-- coffees.module.ts //helps us keep our code organized and establish clear boundaries for the application and its functionality.
    |-- coffees.controller.ts
    |-- coffees.service.ts
    |-- dto
    |-- create-coffee.dto
    |-- update-coffee.dto //Different DTOs should be placed in separate files

    How to generate: Use nest cli: nest generate class path and name.dto --no-spec , abbreviated as nest g class path and name.dto --no-spec . For example, nest g class coffees/dto/create-coffee.dto --no-spec . --no-spec was added to avoid generating test files.


  3. DTO document

    In /dto/create-coffee.dto:

    1
    2
    3
    4
    5
    export class CreateCoffeeDto {
    name: string;
    brand: string;
    flavors: string[];
    }

  1. Use in controller DTO

    In coffees.controller.ts:

    1
    2
    3
    4
    @Post()
    create(@Body() createCoffeeDto: CreateCoffeeDto) {
    return this.coffeesService.create(createCoffeeDto); //Note that a DTO instance is used when creating here
    }

  2. DTO Tag Properties

    readonly guarantees that the property is not modified

    1
    2
    3
    4
    5
    export class CreateCoffeeDto {
    readonly name: string;
    readonly brand: string;
    readonly flavors: string[];
    }

    ? guarantees that the attribute is optional. This guarantees that @Patch can update any tiny part.

    1
    2
    3
    4
    5
    export class UpdateCoffeeDto {
    name?: string; //Note? Location
    brand?: string; //Attention? Location
    flavors?: string[]; //Note? Location
    }

  1. Data validation: ValidationPipe

    The downside of DTOs is that we don’t know who or what is calling these requests, how to make sure the incoming data has the correct shape, or if the data is missing necessary fields.

    NestJS provides ValidationPipe to solve this exact problem. ValidationPipe provides a convenient way to enforce validation rules on incoming client payloads (data). Programmers can specify these rules by using simple comments in the DTO.


  1. Application is set to use ValidationPipe

    In src/main.ts, add app.useGlobalPipes(new ValidationPipe()) .

    Also install two packages: npm i class-validator class-transformer.

    1
    2
    3
    4
    5
    6
    7
    import { ValidationPipe } from '@nestjs/common';

    async function bootstrap(){
    const app = await NestFactory.create(AppModule);
    app.useGlobalPipes(new ValidationPipe())
    await app.listen(3000);
    }

    It can then be modified in the DTO file. See the class-validator documentation to see all decorators.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import { IsString } from 'class-validator';
    export class CreateCoffeeDto {
    @IsString()
    readonly name: string;

    @IsString()
    readonly brand: string;

    @IsString({ each: true }) //Indicates that each item in the array is expected to be a string.
    readonly flavors: string[];
    }

    If the input data does not conform to the rules, it will automatically respond with a 400 BadRequest code and indicate the reason in the message.


  1. Create’s validation is reused for Update

    The writing in “Error” section is cumbersome and can be simplified. nestjs provides several utility functions as part of the package @nestjs/mapped-types. These functions help us quickly perform common conversions of these types.

    First install the package: npm i @nestjs/mapped-types. Then change the update-coffee.dto.ts file to:

    1
    2
    3
    4
    5
    6
    import {PartialType} from '@nestjs/mapped-types';
    import {CreateCoffeeDto} from './create-coffee.dto';

    export class UpdateCoffeeDto extends PartialType(CreateCoffeeDto){

    }

    The PartialType function is very useful because what it does for us is return the type of the class we pass to it, with all properties set to optional. And it also inherits all validation rules applied through the decorator. It also dynamically adds a single additional validation rule to each field @IsOptional() .

    Some properties we hope cannot be updated, such as user_id. In this case the @Exclude() decorator can be used to prevent property updates.


  2. ValidationPipe whitelist

    Whitelisting is used to filter out properties that should not be received by method handlers. By whitelisting acceptable properties, any properties not included in the whitelist are automatically stripped from the resulting object.

    Whitelisting is used by entering some options into the ValidationPipe. In main.ts:

    1
    2
    3
    4
    5
    6
    async function bootstrap(){
    const app = await NestFactory.create(AppModule);
    app.useGloblePipes(new ValidatePipe({
    whitelist: true, //add whitelist
    })
    }

    The whitelist also has an option to stop processing requests and throw an error if there are any non-whitelisted attributes:

    1
    2
    3
    4
    5
    6
    7
    async function bootstrap(){
    const app = await NestFactory.create(AppModule);
    app.useGloblePipes(new ValidatePipe({
    whitelist: true,
    forbidNonWhitelisted: true, // throws an error when there is a non-whitelisted attribute
    })
    }

  1. Guaranteed that the shape of the payload (the incoming object, i.e. the payload) is as expected

    As in @Post(), createCoffeeDto is not an instance of CreateCoffeeDto. This means that the payload is the shape of CreateCoffeeDto, but not an instance.

    ValidationPipe can help us convert this object to what we expect. Enable globally:

    1
    2
    3
    4
    5
    6
    7
    8
    async function bootstrap(){
    const app = await NestFactory.create(AppModule);
    app.useGloblePipes(new ValidatePipe({
    whitelist: true,
    transform: true, // set here
    forbidNonWhitelisted: true,
    })
    }

    This transform can also perform primitive type conversions for things like boolean and number. By default, the parameters in path and query are of type string.

    When adding transform: true, if we change the string in findOne(@Param('id') id: string){} to a number, it will cause problems because coffeesService.findOne() expects a string as a parameter (custom in nestJS.4). Because at this time id has been changed to number type.

    This feature has a very slight performance impact.


  1. Automatic conversion type

    1
    @Type(() => Number) //If the string type is passed, no error will be reported, and it will be automatically converted to the number type

  2. Validation of nested objects

    1
    2
    3
    4
    5
    @IsObject()
    @IsNotEmpty()
    @ValidateNested({ each: true }) //Must have this
    @Type(() => Design)
    readonly design: Design;

Share