Skip to main content

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

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);
}
@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';
}

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,
});
}