Is there any way to check validation with static class?
The Decorators are not valid here. ts(1206) error occurs only with static class.
How to fix it?
And how you guys create request and response dto in NestJS?
so far, I've sticked with static class but not sure this is correct way.
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
export class CreateBoardDto {
static Request = class {
#IsString()
#IsNotEmpty()
writer: string;
#IsString()
#IsNotEmpty()
title: string;
#IsString()
#IsNotEmpty()
contents: string;
};
static Response = class {
#IsNumber()
id: number;
#IsString()
#IsNotEmpty()
writer: string;
#IsString()
#IsNotEmpty()
title: string;
#IsString()
#IsNotEmpty()
contents: string;
};
}
Data object (dto) is usually the data received from the request body, so when we create an end-point for a POST request we receive its body as dto, example :
import {Body,Res,Req} from '#nestjs/common'
import { Request, Response } from 'express';
import { createBoardDto } from '../dto/file_name';
import { BoardService } from './board.service'; // leave this for now
export class BoardController {
constructor(private readonly boardService: BoardService) {}
#Post('add')
async addBoard(#Body()dto: CreateBoardDto, #Req() req: Request #Res() res: Response
) {
// your add logic here ... it's better to pass by a service class something like
return await this.boardService.addBoard(req: Request,res: Response,dto: createBoardDto);
// you can pass req and res if the Request and Response are needed
}
}
Here the compiler checks if body is a valid class CreateBoardDto.
And from your service class you manage your logic and then you return an object as final result (you don't need to validate it) you can return the new created board or a simple message
{
message: 'board has been created'
}
Finally in your createBoardDto class :
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
export class CreateBoardDto {
#IsString()
#IsNotEmpty()
writer: string;
#IsString()
#IsNotEmpty()
title: string;
#IsString()
#IsNotEmpty()
contents: string;
}
You don't need any static members
Related
At the moment, I have a very simple class-validator file with a ValidationPipe in Nest.js as follows:
import {
IsDateString,
IsEmail,
IsOptional,
IsString,
Length,
Max,
} from 'class-validator';
export class UpdateUserDto {
#IsString()
id: string;
#Length(2, 50)
#IsString()
firstName: string;
#IsOptional()
#Length(2, 50)
#IsString()
middleName?: string;
#Length(2, 50)
#IsString()
lastName: string;
#IsEmail()
#Max(255)
email: string;
#Length(8, 50)
password: string;
#IsDateString()
dateOfBirth: string | Date;
}
Lets say in the above "UpdateUserDto," the user passes an "email" field. I want to build a custom validation rule through class-validator such that:
Check if email address is already taken by a user from the DB
If the email address is already in use, check if the current user (using the value of 'id' property) is using it, if so, validation passes, otherwise, if it is already in use by another user, the validation fails.
While checking if the email address is already in use is a pretty simple task, how would you be able to pass the values of other properties within the DTO to a custom decorator #IsEmailUsed
It was pretty simple to solve, I solved it by creating a custom class-validation Decorator as below:
import { PrismaService } from '../../prisma/prisma.service';
import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from 'class-validator';
import { Injectable } from '#nestjs/common';
#ValidatorConstraint({ name: 'Unique', async: true })
#Injectable()
export class UniqueConstraint implements ValidatorConstraintInterface {
constructor(private readonly prisma: PrismaService) {}
async validate(value: any, args: ValidationArguments): Promise<boolean> {
const [model, property = 'id', exceptField = null] = args.constraints;
if (!value || !model) return false;
const record = await this.prisma[model].findUnique({
where: {
[property]: value,
},
});
if (record === null) return true;
if (!exceptField) return false;
const exceptFieldValue = (args.object as any)[exceptField];
if (!exceptFieldValue) return false;
return record[exceptField] === exceptFieldValue;
}
defaultMessage(args: ValidationArguments) {
return `${args.property} entered is not valid`;
}
}
export function Unique(
model: string,
uniqueField: string,
exceptField: string = null,
validationOptions?: ValidationOptions,
) {
return function (object: any, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [model, uniqueField, exceptField],
validator: UniqueConstraint,
});
};
}
However, to allow DI to that particular Decorator, you need to also add this to your main.ts bootstrap function:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
...
// Line below needs to be added.
useContainer(app.select(AppModule), { fallbackOnErrors: true });
...
}
Also, make sure to import the "Constraint" in the app module:
#Module({
imports: ...,
controllers: [AppController],
providers: [
AppService,
PrismaService,
...,
// Line below added
UniqueConstraint,
],
})
export class AppModule {}
Finally, add it to your DTO as such:
export class UpdateUserDto {
#IsString()
id: string;
#IsEmail()
#Unique('user', 'email', 'id') // Adding this will check in the user table for a user with email entered, if it is already taken, it will check if it is taken by the same current user, and if so, no issues with validation, otherwise, validation fails.
email: string;
}
Luckily for us, the class-validator provides a very handy useContainer function, which allows setting the container to be used by the class-validor library.
So add this code in your main.ts file (app variable is your Nest application instance):
useContainer(app.select(AppModule), { fallbackOnErrors: true });
It allows the class-validator to use the NestJS dependency injection container.
#ValidatorConstraint({ name: 'emailId', async: true })
#Injectable()
export class CustomEmailvalidation implements ValidatorConstraintInterface {
constructor(private readonly prisma: PrismaService) {}
async validate(value: string, args: ValidationArguments): Promise<boolean> {
return this.prisma.user
.findMany({ where: { email: value } })
.then((user) => {
if (user) return false;
return true;
});
}
defaultMessage(args: ValidationArguments) {
return `Email already exist`;
}
}
Don't forget to declare your injectable classes as providers in the appropriate module.
Now you can use your custom validation constraint. Simply decorate the class property with #Validate(CustomEmailValidation) decorator:
export class CreateUserDto {
#Validate(customEmailValidation)
email: string;
name: string;
mobile: number;
}
If the email already exists in the database, you should get an error with the default message "Email already exists". Although using #Validate() is fine enough, you can write your own decorator, which will be much more convenient. Having written Validator Constraint is quick and easy. We need to just write decorator factory with registerDecorator() function.
export function Unique(validationOptions?: ValidationOptions) {
return function (object: any, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: CustomEmailvalidation,
});
};
}
As you can see, you can either write new validator logic or use written before validator constraint (in our case - Unique class).
Now we can go back to our User class and use the #Unique validator instead of the #Validate(CustomEmailValidation) decorator.
export class CreateUserDto {
#Unique()
email: string;
name: string;
mobile: number;
}
I think your first use case (Check if email address is already taken by a user from the DB), can be solved by using custom-validator
For the second one there is no option to get the current user before the validation. Suppose you are getting the current user using the #CurrentUser decorator. Then once the normal dto validation is done, you need to check inside the controller or service if the current user is accessing your resource.
I have created a sample application to save data for a user. I wanted to know how to validate it before saving and showing the response in JSON. I have got a reference link but its not using a controller.
Please suggest a solution using a controller. Below is my code
Entity file:
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName: string;
#Column()
age: number;
}
Controller file:
import {getRepository} from "typeorm"; import {NextFunction, Request, Response} from "express"; import {User} from "../entity/User";
export class UserController {
private userRepository = getRepository(User);
async all(request: Request, response: Response, next: NextFunction) {
return this.userRepository.find();
}
async one(request: Request, response: Response, next: NextFunction) {
return this.userRepository.findOne(request.params.id);
}
async save(request: Request, response: Response, next: NextFunction) {
return this.userRepository.save(request.body);
}
async remove(request: Request, response: Response, next: NextFunction) {
let userToRemove = await this.userRepository.findOne(request.params.id);
await this.userRepository.remove(userToRemove);
}
}
I wanted to validate firstname and lastname as mandatory i.e. no blank value should be accepted and age should take no blank values and numbers only.
Yes you can by using validate function in model class:
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName: string;
#Column()
age: number;
#BeforeInsert()
#BeforeUpdate()
async validate() {
await validateOrReject(this);
}
}
You can use the class-validator and class-transformer package.
Define the structure of JSON as a typescript class and add decorators from class-validator package.
export class UserStructure {
#IsString()
firstName: string;
#IsString()
lastName: string;
age: number;
}
Use class transformer to convert plain object to UserStructure Class and then pass the class to validate function which is imported from class-validator package for performing validation
import { plainToClass } from 'class-transformer';
import {validate} from 'class-validator';
//Place the following code inside your controller
let userStructureClass = plainToClass(UserStructure , req.body); //convert req.body plain object to UserStructure Class
let errors = await validate(userStructureClass);
if(errors.length > 0) req.json(errors)
class-validator : https://www.npmjs.com/package/class-validator
class-transformer : https://www.npmjs.com/package/class-transformer
NOTE : validate method only works for classes that is why we converted req.body plain object to class using class-transformer and then performed validation.
These packages uses decorators so make sure to place the following in tsconfig.json
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
I feel like a combination of this thread and this thread is what I need to implement, I'm having trouble drawing them together.
I have a DTO that contains an enum.
Using Postman, I am sending a PurchasableType of FOO and expecting to get an error of some sort. Reading through the above links, it seems like the process is quite involved; which makes me thing I'm completely missing the point.
How can I use the validation pipe(s) to make sure only the values in the purchasable-type.enum.ts are allowed?
Thank you for any suggestions!
// create-order.dto.ts
import { IsEmail, IsNotEmpty, IsEnum } from 'class-validator';
import { PurchasableType } from '../enum/purchaseable-type.enum';
export class CreateOrderDto {
#IsNotEmpty()
readonly userId: string;
#IsNotEmpty()
readonly locationId: string;
#IsNotEmpty()
#IsEnum(PurchasableType)
readonly purchasableType: PurchasableType;
#IsNotEmpty()
#IsEmail()
readonly email: string;
}
// purchasable-type.enum.ts
export enum PurchasableType {
CLINIC = 'CLINIC',
EVENT = 'EVENT',
LESSON = 'LESSON',
RESERVATION = 'RESERVATION',
TEAM = 'TEAM',
}
EDIT
It seems I was also not defining the entity correctly, and that may have been the main issue. I am still curious if my implementation good/bad.
// order.entity.ts
...
import { PurchasableType } from '../enum/purchaseable-type.enum';
#Entity()
export class Order extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column({
type: 'enum',
enum: PurchasableType,
})
Now when I send a purchasableType of foo I am getting a 500 error. If I send any valid value that is within the enum I am getting a 200/201.
EDIT 2
Sure - here is a bit wider view of what I've got. Everything seems to be working properly, I'd just like to have a better grasp of what was really happening.
// event.controller.ts
#Post('/:id/orders')
async purchaseEventTickets(#Body() createOrderDto: CreateOrderDto):
Promise<Order> {
return await this.eventService.purchaseEventTickets(createOrderDto);
}
// create-order.dto.ts
export class CreateOrderDto {
#IsNotEmpty()
#IsEnum(PurchasableType)
readonly purchasableType: PurchasableType;
}
// event.service.ts
async purchaseEventTickets(createOrderDto: CreateOrderDto): Promise<Order> {
...
return await this.orderRepository.createOrder(createOrderDto);
}
// order.repository.ts
async createOrder(createOrderDto: CreateOrderDto): Promise<Order> {
const { purchasableType } = createOrderDto;
const order = this.create();
order.purchasableType = purchasableType;
try {
await order.save();
} catch (error) {
this.logger.error(`Failed to create the order: ${error.stack}`);
throw new InternalServerErrorException();
}
return order;
}
Using Postman, if I send an invalid value of "Foo" as a PurchasableType I get the expected error.
It took me a while to find a good solution.
#ApiProperty({
description: 'List of enums',
isArray: true,
enum: MyEnum
})
#IsEnum(MyEnum, { each: true })
prop: MyEnum[];
Here is what your create-dto looks like that contains an enum.
// create-order.dto.ts
import { IsEmail, IsNotEmpty, IsEnum } from 'class-validator';
import { PurchasableType } from '../enum/purchaseable-type.enum';
export class CreateOrderDto {
...
#IsNotEmpty()
#IsEnum(PurchasableType)
readonly purchasableType: PurchasableType;
}
Here is what that enum file looks like:
// purchasable-type.enum.ts
export enum PurchasableType {
CLINIC = 'CLINIC',
EVENT = 'EVENT',
LESSON = 'LESSON',
RESERVATION = 'RESERVATION',
TEAM = 'TEAM',
}
From there I can confidently expect the value of the enum to be one of the above values. If some other value comes through, nest will throw a validation error.
Additionally, If you are attempting to use a nested object (or something with multiple attributes or an array) you can do something like this in your DTO:
import { PurchasableType } from '../interface/purchasable-type.interface';
...
#ApiProperty()
#IsArray()
#ArrayMinSize(7)
#ArrayMaxSize(7)
#ValidateNested({ each: true })
#Type(() => PurchasableType)
#IsNotEmpty()
readonly PurchasableType: PurchasableType[];
...
#IsArray()
#IsEnum(enum, { each: true })
prop: enum[]
#IsEnum(myEnum, { each: true })
#Transform((value) => myEnum[value])
tags: myEnum[];
None of the above worked for me, I did it this way:
#Column({ type: 'enum', enum: MyEnum, array: true })
myProperty: MyEnum[];
I create POST endpoint to create a new entity.
I also created schema for mongoose with field userId (to connect this entity to specified user) and DTO which I use on my POST method.
#UseGuards(JwtAuthGuard)
#Post("/")
createAction(#Request() req, #Body() createActionDto: CreateActionDto) {
return this.actionService.createAction(req?.user?.userId, createActionDto);
}
DTO:
import { IsString, IsNumber, IsUrl } from 'class-validator';
export class CreateActionDto {
userId: string;
#IsString()
name: string;
#IsNumber()
timeStart: number;
}
Schema:
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
#Schema()
export class Action extends Document {
#Prop()
userId: string;
#Prop()
name: string;
#Prop()
timeStart: number;
}
export const ActionSchema = SchemaFactory.createForClass(Action)
In the req property I have userId. What is the best way to create an entity and attach userId extracted from token?
Should I pass req to the service, and in the service set userId property on DTO like this?:
#Injectable()
export class ActionService {
constructor(
#InjectModel(Action.name) private actionModel: Model<Action>,
) { }
async createAction(req: string, createActionDto: CreateActionDto) {
createActionDto.userId = req.user.userId
// ... save to mongoose createActionDto
}
}
Is it a correct solution or there is another, a better way to deal with it?
Personally I would set the userId in the controller in order to not having to pass it around:
#UseGuards(JwtAuthGuard)
#Post("/")
createAction(#Request() req, #Body() createActionDto: CreateActionDto) {
createActionDto.userId = req?.user?.userId;
return this.actionService.createAction(createActionDto);
}
If you have many different controllers and DTOs that require the userId you could also define an Interceptor and do it there in order to reduce duplication:
#Injectable()
export class SetUserIdInterceptor implements NestInterceptor {
public intercept(_context: ExecutionContext, $next: CallHandler): Observable<any> {
const request: any = _context.switchToHttp().getRequest(); //instead of any you could also define a super-class for all DTOs that require the `userId`-property
request.body?.userId = req?.user?.userId;
return $next;
}
}
You can then use this interceptor on your route as follows:
#UseGuards(JwtAuthGuard)
#Post("/")
#UseInterceptors(SetUserIdInterceptor)
createAction(#Body() createActionDto: CreateActionDto) {
return this.actionService.createAction(createActionDto)
}
When I went through Pipes documentation I noticed that I can't make #IsInt() validation for application/x-www-form-urlencoded request correctly, cause all values which I passed I receive as string values.
My request data looks like this
My DTO looks like
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
#IsString()
readonly name: string;
#IsInt()
readonly age: number;
#IsString()
readonly breed: string;
}
Validation pipe contains next code
import { PipeTransform, Pipe, ArgumentMetadata, BadRequestException } from '#nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
#Pipe()
export class ValidationPipe implements PipeTransform<any> {
async transform(value, metadata: ArgumentMetadata) {
const { metatype } = metadata;
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.find((type) => metatype === type);
}
}
When I debug this pipe I noticed this state
Where:
value - request body value
object - transformed via class-transformer value
errors - error object
As you can see errors tell to us that age must be an integer number.
How can I pass #IsInt() validation for application/x-www-form-urlencoded request?
Libraries versions:
#nestjs/common#4.6.4
class-transformer#0.1.8
class-validator#0.8.1
P.S: I also create a repository where you can run application to test bug. Required branch how-to-pass-int-validation
UPD: after making changes from accepted answer I faced with problem that I put wrong parsed data to storage. Recorded example
Is it possible to get well parsed createCatDto or what I need to do to save it with correct type structure?
All values from a application/x-www-form-urlencoded request are always strings.
So, you could do the following:
import { Transform } from 'class-transformer';
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
#IsString()
readonly name: string;
#Transform(value => Number.isNan(+value) ? 0 : +value) // this field will be parsed to integer when `plainToClass gets called`
#IsInt()
readonly age: number;
#IsString()
readonly breed: string;
}
Adding #Type(() => Number) resolved the issue for me.
import { Type } from 'class-transformer';
import { IsInt, IsNotEmpty } from 'class-validator';
#IsNotEmpty({ message: '' })
#Type(() => Number)
#IsInt({ message: '' })
project: number;