101. Entity, Dto and Validation
Intro
- Entities are used to construct models from DB perspective.
- can contain DB related decorators and validations options
- Data Transfer Objects (DTOs) are used for input validation and defining the shape of data passed to and from the API.
Defining Entities
//inside users.entity.ts
export class Users{
id: number;
name: string;
age: number;
email: string;
}
//or using interface
export interface Users{
id: number;
name: string;
age: number;
email: string;
}
Defining Create and Update Dto
- Validation | NestJS - A progressive Node.js framework
- When building input validation types (also called DTOs), it's often useful to build create and update variations on the same type.
- For example, the create variant may require all fields, while the update variant may make all fields optional.
- To make the creation of these files easier nest provides,
Mappeed-types
package, which provides utility types such as- partial types, pick-type, omit-types…
#since mapped-types isn't shipped with nest by default you need to install it
npm i @nestjs/mapped-types
//creating create dto
export class CreateUsersDto extends OmitType(Users, ['id']){}
//or
export class CreateUsersDto extends PickType(Users, ['name', 'age'])
//creating update dto
export class UpdateUsersDto extends PartialType(Users){}
Using DTOs in Controller
@Post()
create(@Body() user: CreateUserDto) {
return this.usersService.create(user);
}
@Patch(':id')
update(@Param('id') id: string, @Body() user: UpdateUserDto) {
return this.usersService.update(+id, user);
}
Validation
- Validation | NestJS - A progressive Node.js framework
- To automatically validate incoming requests, Nest provides several pipes available right out of the box:
- ParseIntPipe
- ParseBoolPipe
- ParseArrayPipe
- ParseUUIDPipe
- …
- Additionally nest also works with
class-validator
Using Validation Pipes
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
//if id is invalid the following response will be returned
{
"statusCode": 400,
"message": "Validation failed (numeric string is expected)",
"error": "Bad Request"
}
- The syntax to use
ParseBoolPipe
and other nest built in validation pipes is similar
Using Class-validator
npm i class-transformer class-validator
//We will start by binding ValidationPipe at the application level, thus ensuring all endpoints are protected from receiving incorrect data.
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe()); <---
await app.listen(3000);
}
bootstrap();
import { IsString, IsNumberString, IsEmail, IsNotEmpty } from 'class-validator';
export class Users{
@IsNotEmpty()
@IsNumberString()
id: number;
@IsNotEmpty()
name: string;
@IsNotEmpty()
age: number;
@IsEmail()
email: string;
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return 'This action adds a new user';
}
- With these rules in place, if a request hits our endpoint with an invalid email property in the request body, the application will automatically respond with a 400 Bad Request code, along with the following response body
{
"statusCode": 400,
"error": "Bad Request",
"message": ["email must be an email"]
}
Stripping Properties
- Validation | NestJS - A progressive Node.js framework
- how to remove unwanted properties from request body
- using
whitelist
on our validation, we can tell nest.js to strip away unwanted properties based on DTO(data transfer objects) definition
- using
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true
}));
await app.listen(3000);
}
bootstrap();
example
- if user is sending
- {age: 12, name: 'myname', country: 'US'}
- and backend is defining req as
- {age, name}
- then the input our function will receive will be
- only {age: 12, name: 'myname'}
- to fail the request altogether, if client is providing unknown properties use, forbidNonWhitelisted
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
forbidNonWhitelisted: true
}));
await app.listen(3000);
}
bootstrap();
- Alternatively you can white list a request at controller level
@Post()
@UsePipes(new ValidationPipe({ whitelist: true }))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
Transform Payload Objects
- Validation | NestJS - A progressive Node.js framework
- how to transform payloads to DTO instances.
- add transform: true option to globalpipes validation
- this will try to transform all incoming request params, query, and body to specified dto types
- ex: incoming string id will be transformed to number
- cons:
- transform can be a heavy operation, and can lead to slow apis
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
transform: true,
}));
//before transform
@GET(':id')
findAll(QParam('id') id: string)
//after transform
@Get(':id')
findAll(QParam('id') id: number) //we can change id to its true type
//before transform
@Post()
create(QBody dto: DTO){
dto instanceof DTO //false
}
//after transform
@Post()
create(QBody dto: DTO){
dto instanceof DTO //true
}
- We can apply transform at controller level as well
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
- Or at a parameter level
@Get(':id')
findOne(
@Param('id', ParseIntPipe) id: number,
@Query('sort', ParseBoolPipe) sort: boolean,
) {
console.log(typeof id === 'number'); // true
console.log(typeof sort === 'boolean'); // true
return 'This action returns a user';
}
- Learn how to validate array of objects
Pagination
- create a dto type for pagination
- add
explicittypetranform
to true in validation pipe at main.ts - pass the limit and offset properties to DB queries
import { IsOptional, IsPositive } from 'class-validator';
export class PaginationDTO {
@IsOptional()
@IsPositive()
offset: number;
@IsOptional()
@IsPositive()
limit: number;
}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: {
enableImplicitConversion: true, //<----
}
}));
await app.listen(3000);
}
bootstrap();
@Get()
findAll(@Query() paginationQuery: PaginationDTO) {
return this.coffesService.findAll(paginationQuery);
}
findAll(PaginationDTO: PaginationDTO) {
const { offset, limit } = PaginationDTO;
return this.coffesRepository.find({
relations: ['flavours'],
skip: offset,
take: limit,
});
}