Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validation does not work with Partial<DTO> - NestJS

I want to apply server-side validation on my CRUD API. The entity in question is called Employee. I am using an employee.dto (shown below) for the create and update endpoints.

The class-validator package works fine on the create method but ignores all rules in the DTO when I use it with Partial<EmployeeDTO> in the update method.

Please use the code below for reference.

Packages

"class-transformer": "^0.2.3",
"class-validator": "^0.10.0",

Employee DTO

import { IsString, IsNotEmpty, IsEmail, IsEnum } from 'class-validator';

import { EmployeeRoles } from '../../entities/employee.entity';

export class EmployeeDTO {
  @IsString()
  @IsEmail()
  @IsNotEmpty()
  email: string;

  @IsString()
  @IsNotEmpty()
  password: string;

  @IsString()
  @IsNotEmpty()
  username: string;

  @IsString()
  @IsNotEmpty()
  fullName: string;

  @IsString()
  @IsNotEmpty()
  @IsEnum(EmployeeRoles)
  role: string;
}

Employee Controller

import {
  Controller,
  Param,
  Post,
  Body,
  Put,
  UsePipes,
} from '@nestjs/common';

import { EmployeeDTO } from './dto/employee.dto';
import { EmployeeService } from './employee.service';
import { ValidationPipe } from '../shared/pipes/validation.pipe';

@Controller('employee')
export class EmployeeController {
  constructor(private employeeService: EmployeeService) {}

  @Post()
  @UsePipes(ValidationPipe)
  addNewEmployee(@Body() data: EmployeeDTO) {
    return this.employeeService.create(data);
  }

  @Put(':id')
  @UsePipes(ValidationPipe)
  updateEmployee(@Param('id') id: number, @Body() data: Partial<EmployeeDTO>) {
    return this.employeeService.update(id, data);
  }
}

Possible Solution

I work around I can think of is creating separate DTOs for create and update methods, but I don't like the idea of repeating the code.

like image 324
Haseeb Burki Avatar asked Sep 05 '19 01:09

Haseeb Burki


People also ask

When to use DTO in NestJS?

DTO is used in order to validate incoming requests. The DTO on its own is more of a guideline for the developer and those who consume the API to know what kind of shape the request body expects to be, it doesn't actually run any validations on its own!!!.

What is DTO NestJS?

A DTO is an object that defines how the data will be sent over the network. We could determine the DTO schema by using TypeScript interfaces, or by simple classes. Interestingly, we recommend using classes here.

What is ValidationPipe in NestJS?

The in-built NestJS ValidationPipe is a great way to handle validations in an application. Validations are an extremely important aspect of any production-level application. Often, developers have to write large amounts of code to handle even basic validations. This is largely counter-productive.


2 Answers

In order to achieve partial validation, you can use PartialType utility function. You can read about it here: https://docs.nestjs.com/openapi/mapped-types#partial

You would need to create another class:

export class UpdateEmployeeDTO extends PartialType(EmployeeDTO) {}

and then in your controller, you need to replace the type of @Body data Partial<EmployeeDTO> to UpdateEmployeeDto. It should look like this:

@Patch(':id')
@UsePipes(ValidationPipe)
updateEmployee(@Param('id') id: number, @Body() data: UpdateEmployeeDTO) {
    return this.employeeService.update(id, data);
}

Please keep in mind that you should import PartialType from @nestjs/mapped-types not from @nestjs/swagger like suggested in the documentation. More about this can be found here

like image 121
Milszym Avatar answered Oct 26 '22 21:10

Milszym


For this answer, I'll take a guess and assume that you use the ValidationPipe provided in the NestJS' documentation, or a close derivative.

Your updateEmployee method's argument data type is Partial, which doesn't emit any type metadata. for the ValidationPipe to instantiate it using the class-transformer module, resulting in the class-validator module to validate a plain object, and not an EmployeeDTO.

For the validation to work, the type of the data argument should be a class. You could either make separate DTOs to create and update your entity, or use validation groups if you want to keep a single class.

like image 22
Thibault Burger Avatar answered Oct 26 '22 19:10

Thibault Burger