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.
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 filesHow to generate: Use nest cli:
nest generate class path and name.dto --no-spec
, abbreviated asnest 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.
DTO document
In /dto/create-coffee.dto:
1
2
3
4
5export class CreateCoffeeDto {
name: string;
brand: string;
flavors: string[];
}
Use in controller DTO
In coffees.controller.ts:
1
2
3
4()
create() { () createCoffeeDto: CreateCoffeeDto
return this.coffeesService.create(createCoffeeDto); //Note that a DTO instance is used when creating here
}
DTO Tag Properties
readonly
guarantees that the property is not modified1
2
3
4
5export 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
5export class UpdateCoffeeDto {
name?: string; //Note? Location
brand?: string; //Attention? Location
flavors?: string[]; //Note? Location
}
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.
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
7import { 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
11import { IsString } from 'class-validator';
export class CreateCoffeeDto {
()
readonly name: string;
()
readonly brand: string;
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.
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
6import {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.
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
6async 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
7async 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
})
}
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
8async 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.
Automatic conversion type
1
() => Number) //If the string type is passed, no error will be reported, and it will be automatically converted to the number type (
Validation of nested objects
1
2
3
4
5()
()
each: true }) //Must have this ({
() => Design) (
readonly design: Design;