validate nested objects using class-validator in nest.js controller - node.js

I want to validate body payload using class-validator in a nest.js controller. My currency.dto.ts file is like this:
import {
IsNotEmpty,
IsString,
ValidateNested,
IsNumber,
IsDefined,
} from 'class-validator';
class Data {
#IsNotEmpty()
#IsString()
type: string;
#IsNotEmpty()
#IsNumber()
id: number;
}
export class CurrencyDTO {
#ValidateNested({ each: true })
#IsDefined()
data: Data[];
}
and in my nest.js controller, I use it like this.
#Post()
#UseGuards(new AuthTokenGuard())
#UsePipes(new ValidationPipe())
addNewCurrency(#Req() req, #Body() data: CurrencyDTO) {
console.log('data', data);
}
my validation pipe class is like this:
import {
PipeTransform,
Injectable,
ArgumentMetadata,
BadRequestException,
HttpException,
HttpStatus,
} from '#nestjs/common';
import { validate, IsInstance } from 'class-validator';
import { plainToClass, Exclude } from 'class-transformer';
#Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, metadata: ArgumentMetadata) {
if (value instanceof Object && this.isEmpty(value)) {
throw new HttpException(
`Validation failed: No Body provided`,
HttpStatus.BAD_REQUEST,
);
}
const { metatype } = metadata;
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errorsList = await validate(object);
if (errorsList.length > 0) {
const errors = [];
for (const error of errorsList) {
const errorsObject = error.constraints;
const { isNotEmpty } = errorsObject;
if (isNotEmpty) {
const parameter = isNotEmpty.split(' ')[0];
errors.push({
title: `The ${parameter} parameter is required.`,
parameter: `${parameter}`,
});
}
}
if (errors.length > 0) {
throw new HttpException({ errors }, HttpStatus.BAD_REQUEST);
}
}
return value;
}
private toValidate(metatype): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.find(type => metatype === type);
}
private isEmpty(value: any) {
if (Object.keys(value).length > 0) {
return false;
}
return true;
}
}
This validation pipe works fine for all except for nested objects. Any idea what am I doing wrong here?
My body payload is like this:
{
"data": [{
"id": 1,
"type": "a"
}]
}

Try specifying the nested type with #Type:
import { Type } from 'class-transformer';
export class CurrencyDTO {
#ValidateNested({ each: true })
#Type(() => Data)
data: Data[];
}
For a nested type to be validated, it needs to be an instance of a class not just a plain data object. With the #Type decorator you tell class-transformer to instantiate a class for the given property when plainToClass is called in your VaildationPipe.
If you are using the built-in ValidationPipe make sure you have set the option transform: true.

At least in my case, the accepted answer needed some more info. As is, the validation will not run if the key data does not exist on the request. To get full validation try:
#IsDefined()
#IsNotEmptyObject()
#ValidateNested()
#Type(() => CreateOrganizationDto)
#ApiProperty()
organization: CreateOrganizationDto;

Related

ERROR [ExceptionsHandler] Cannot query across many-to-many for property permissions

I have a project that I have made the project with nestjs and now I have a problem in method's update and the relation is Many-To-Many when I call Put Api nest gives me below error Note: I have 2 entity Role and Permission that they have many-to-many relation together.
I have a update method in role-service that I commented on it that works well but I have made a abstract class and role-service extended it but the method update doesn't work and give me bellow error
request api => url/api/role/id Body => {name:"admin",permissions:[{"id":1},{"id":2}]}
ERROR [ExceptionsHandler] Cannot query across many-to-many for property permissions
-Role Entity
import { Permission } from 'src/permission/model/permission.entity';
import {
Column,
Entity,
JoinTable,
ManyToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
#Entity('roles')
export class Role {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#ManyToMany((_Type) => Permission, { cascade: true })
#JoinTable({
name: 'role_permissions',
joinColumn: { name: 'role_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'permission_id', referencedColumnName: 'id' },
})
permissions: Permission[];
}
Permission Entity
#Entity('permissions')
export class Permission {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
}
Role Controller
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
UseGuards,
} from '#nestjs/common';
import { AuthGuard } from 'src/auth/auth.guard';
import { RoleCreateDto } from './models/role-create.dto';
import { RoleUpdateDto } from './models/role-update.dto';
import { Role } from './models/role.entity';
import { RoleService } from './role.service';
#UseGuards(AuthGuard)
#Controller('roles')
export class RoleController {
constructor(private roleService: RoleService) {}
#Put(':id')
async update(#Param('id') id: number, #Body() body: RoleUpdateDto) {
await this.roleService.update(id,body);
return await this.roleService.findOne({ id });
}
}
Role service
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { AbstractService } from 'src/common/abstract.service';
import { Repository } from 'typeorm';
import { RoleCreateDto } from './models/role-create.dto';
import { Role } from './models/role.entity';
#Injectable()
export class RoleService extends AbstractService {
constructor(
#InjectRepository(Role) private readonly roleRepository: Repository<Role>,
) {
super(roleRepository)
}
async findOne(condition): Promise<Role> {
return await this.roleRepository.findOne({ where: condition , relations:["permissions"]});
}
// async update(id: number, data:any): Promise<any> {
// console.log(id);
// const role = await this.findOne({id});
// console.log(role);
// role.name = data.name;
// role.permissions= data.permissions;
// const r = await this.roleRepository.preload(role)
// console.log("role",r);
// return await this.roleRepository.save(r);
// }
// async delete(id: number): Promise<any> {
// return await this.roleRepository.delete(id);
// }
}
abstract service
import { Injectable } from '#nestjs/common';
import { Repository } from 'typeorm';
import { PaginatedResult } from './pagibated-result.interface';
#Injectable()
export abstract class AbstractService {
protected constructor(protected readonly repository: Repository<any>) {}
async all(): Promise<any[]> {
return await this.repository.find();
}
async paginate(page = 1): Promise<PaginatedResult> {
const take = 1;
const [data, total] = await this.repository.findAndCount({
take,
skip: (page - 1) * take,
});
return {
data: data,
meta: {
total,
page,
last_page: Math.ceil(total / take),
},
};
}
async create(data): Promise<any> {
return await this.repository.save(data);
}
async findOne(condition): Promise<any> {
return await this.repository.findOne({ where: condition });
}
async update(id: number, data): Promise<any> {
return await this.repository.update({id},data);
}
async delete(id: number): Promise<any> {
return await this.repository.delete(id);
}
}

Is there a way to insert current user into fields in base entity before inserting data

How to to insert current logged in user to createdBy & lastChangedBy fields after creating/updating entity?
In my BaseEntity i've tried
#BeforeInsert()
async insertUser(#GetAuthUserPayload() userPayload: User) {
const user = await this.usersService.findOne({
where: { username: userPayload.username },
});
this.createdBy = user;
this.lastChangedBy = user;
}
But i've found out decorators work only in controllers(in entity they return undefined). Is there any other way than updating DTO in controller or using session?
Since i am using #nestjsx/crud i haven't found any other method than updating DTO. I've managed to solve this issue by creating BaseService:
import { TypeOrmCrudService } from '#nestjsx/crud-typeorm';
import { InjectRepository } from '#nestjs/typeorm';
import { Inject, Injectable, Scope, Type } from '#nestjs/common';
import { CrudRequest, Override } from '#nestjsx/crud';
import { DeepPartial } from 'typeorm';
import { REQUEST } from '#nestjs/core';
import { User } from '../users/entities/user.entity';
export interface IBaseService<T> {}
type Constructor<I> = new (...args: any[]) => I;
export function BaseService<T>(entity: Constructor<T>): Type<IBaseService<T>> {
#Injectable({
scope: Scope.REQUEST,
})
class BaseServiceHost extends TypeOrmCrudService<T> implements IBaseService<T> {
constructor(#InjectRepository(entity) repo, #Inject(REQUEST) readonly request: any) {
super(repo);
}
#Override()
createOne(req: CrudRequest, dto: DeepPartial<T>): Promise<T> {
return super.createOne(req, this.addCreatedByToDTO(dto));
}
#Override()
replaceOne(req: CrudRequest, dto: DeepPartial<T>): Promise<T> {
return super.replaceOne(req, this.addLastChangedByToDTO(dto));
}
#Override()
updateOne(req: CrudRequest, dto: DeepPartial<T>): Promise<T> {
return super.updateOne(req, this.addLastChangedByToDTO(dto));
}
private addCreatedByToDTO(dto: DeepPartial<T>): DeepPartial<T> {
const userUUID: Partial<User> = this.request.user.userUUID;
return { ...dto, createdBy: userUUID };
}
private addLastChangedByToDTO(dto: DeepPartial<T>): DeepPartial<T> {
const userUUID: Partial<User> = this.request.user.userUUID;
return { ...dto, lastChangedBy: userUUID };
}
}
return BaseServiceHost;
}
Later on i just extend my service like:
#Injectable()
export class ExampleService extends BaseService(ExampleEntity) {}

Convert stringified JSON to Object using class-transformer

There is a nest.js project, where in the request body we expect an object, one property of this object contains stringified JSON value. The idea is to convert this string to an object, validate it and pass to controller as an object
ValidationPipe set up:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
transform: true,
}),
);
DTO:
#Transform(parseJson, { toClassOnly: true })
#Type(() => AdditionalInfo)
#IsNotEmptyObject()
#ValidateNested()
additionalInfo: AdditionalInfo;
parseJson function
export function parseJson(options: {
key: string;
value: string;
obj: string | Record<string, any>;
}): Record<string, any> {
try {
return JSON.parse(options.value);
} catch (e) {
throw new BadRequestException(`${options.key} contains invalid JSON `);
}
}
For some reason in the controller the parsed value gets lost, and we receive an empty object.
Looks like #Transform works well with primitives only.
Decided to create ParseJsonPipe and use it instead.
Usage (in the controller):
#Body('additionalInfo', new ParseJsonPipe(), new ValidationPipe(AdditionalInfoDto)) additionalInfo: AdditionalInfo,
ParseJsonPipe:
import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '#nestjs/common';
#Injectable()
export class ParseJsonPipe implements PipeTransform<string, Record<string, any>> {
transform(value: string, metadata: ArgumentMetadata): Record<string, any> {
const propertyName = metadata.data;
try {
return JSON.parse(value);
} catch (e) {
throw new BadRequestException(`${propertyName} contains invalid JSON `);
}
}
}
ValidationPipe implements PipeTransform from #nestjs/common, transform function looks like that:
async transform(value: any): Promise<any> {
if (!this.metaType) { // AdditionalInfoDto
return value;
}
const object = plainToClass(this.metaType, value);
const errors = await validate(object);
if (errors.length > 0) {
const message = this.getErrorMessages(errors);
throw new BadRequestException({ message });
}
return value;
}

NestJS swagger module not working properly with generic crud controller

I'm trying to create a generic crud controller that use a generic crud service in a NestJS application. It works properly but the swagger module doesn't generate the documentation about the REST services parameters correctly.
This is the service:
import { Model, Document } from "mongoose";
export abstract class CrudService<CrudModel extends Document, CreateDto, UpdateDto> {
constructor(protected readonly model: Model<CrudModel>) {}
async findAll(): Promise<CrudModel[]> {
return this.model.find().exec();
}
async create(dto: CreateDto): Promise<CrudModel> {
const createdDto = new this.model(dto);
return createdDto.save();
}
async update(id: any, dto: UpdateDto): Promise<CrudModel> {
return this.model.findOneAndUpdate({ _id: id }, dto, { new: true });
}
async delete(id: any): Promise<boolean> {
const deleteResult = await this.model.deleteOne({ _id: id });
return deleteResult.ok === 1 && deleteResult.deletedCount === 1;
}
}
This is the controller:
import { Body, Delete, Get, Param, Post, Put } from "#nestjs/common";
import { Document } from "mongoose";
import { CrudService } from "./crud-service.abstract";
export abstract class CrudController<CrudModel extends Document, CreateDto, UpdateDto> {
constructor(protected readonly service: CrudService<CrudModel, CreateDto, UpdateDto>) {}
#Get()
async findAll(): Promise<CrudModel[]> {
return this.service.findAll();
}
#Post()
async create(#Body() dto: CreateDto): Promise<CrudModel> {
return this.service.create(dto);
}
#Put(':id')
async update(#Param('id') id: string, #Body() dto: UpdateDto): Promise<CrudModel> {
return this.service.update(id, dto);
}
#Delete(':id')
async delete(#Param('id') id: string): Promise<boolean> {
return this.service.delete(id);
}
}
I found this issue on Github repo: https://github.com/nestjs/swagger/issues/86
The last comment mentions a solution using mixins but I can't figure it out how to adapt it to my needs
I eventually went to describing my own schema.
Here is the custom decorator (NestJs example)
import { applyDecorators } from '#nestjs/common';
import { ApiOkResponse, getSchemaPath } from '#nestjs/swagger';
export const OpenApiPaginationResponse = (model: any) => {
return applyDecorators(
ApiOkResponse({
schema: {
properties: {
totalPages: {
type: 'number'
},
currentPage: {
type: 'number'
},
itemsPerPage: {
type: 'number'
},
data: {
type: 'array',
items: { $ref: getSchemaPath(model) }
}
}
}
})
);
};
And here is a example of how it is applied to a controller
#OpenApiPaginationResponse(DTOHere)
public async controllerMethod() {}
Hope this helps you out
I had the same problem recently, I found the solution with Decorators in Nestjs because for native properties Swagger can't recognize our Generic Type Class < T >.
I will show you how I could implement my solution with a Parameterized Pagination Class.
Specify our Pagination Class
export class PageDto<T> {
#IsArray()
readonly data: T[];
#ApiProperty({ type: () => PageMetaDto })
readonly meta: PageMetaDto;
constructor(data: T[], meta: PageMetaDto) {
this.data = data;
this.meta = meta;
}
}
Our parametrized type is data.
Create our Decorator class, that will map our parametrized type data
export const ApiOkResponsePaginated = <DataDto extends Type<unknown>>(
dataDto: DataDto,
) =>
applyDecorators(
ApiExtraModels(PageDto, dataDto),
ApiOkResponse({
schema: {
allOf: [
{ $ref: getSchemaPath(PageDto) },
{
properties: {
data: {
type: 'array',
items: { $ref: getSchemaPath(dataDto) },
},
},
},
],
},
}),
);
In this decorator we used the definition of SwaggerDocumentation for specify what will be our class that Swagger will be mapped.
Add our Decorator ApiOkResponsePaginated class in our Controller.
#Get('/invoices')
#ApiOkResponsePaginated(LightningInvoice)
async getAllInvoices(
#Auth() token: string,
#Query() pageOptionsDto: any,
): Promise<any> {
return this.client.send('get_invoices', {
token,
pageOptionsDto,
});
}
And that's how you can visualize in Swagger the representation of PageDto<LightningInvoice> response.
I hope this answer help you in your code.

Modify DTO properties with decorator

export class MyDto extends Base{
#ApiModelProperty()
#Expose()
#MyCustomModifier()
readonly code: string = "";
MyCustomModifier(){
// modify
code = someUpdateOnCode()
}
Can we do something like this, so we can update dto properties
#Injectable()
export class JoiValidationPipe implements PipeTransform {
constructor(private readonly schema) {}
transform(value: any, metadata: ArgumentMetadata) {
const { error } = this.schema.validate(value);
if (error) {
console.log(error, 'error');
throw new BadRequestException(error.message);
}
// some changing value.code = someUpdateOnCode()
return value;
}
}
and use your pipe like this
import * as Joi from '#hapi/joi';
#Put('')
#UsePipes(
new JoiValidationPipe(
Joi.object().keys({
code: Joi.string()
.min(3)
.max(250)
.allow('')
.optional()
)
})
async someControler(){}

Resources