I have a DTO
export class UpdateUserRoleDTO {
#ApiProperty()
#IsNotEmpty()
readonly userId:number;
#ApiProperty()
#IsNotEmpty()
#IsNumber()
readonly roleId: number;
}
My controller looks like this
#UsePipes(new ValidationPipe())
#Post('/update')
async updateUser(#Body() updateUserDto: UpdateUserDTO): Promise<User> {
return await this.userService.updateUser(updateUserDto);
}
Whenever client sends a request with the following payload
payloadObj = {
userId : 1,
roleId : 1,
xyz : 'assddcds',
someotherkey : 'fsdvs'
}
It's hitting my service file .I want to avoid this make sure only parameter mentioned in DTO should be passed else it should give 400
given your code I'd pass the whitelist option set to true to the ValidationPipe you're instantiating, like so in your controller:
controller.ts
#UsePipes(new ValidationPipe({ whitelist: true }))
#Post('/update')
async updateUser(#Body() updateUserDto: UpdateUserDTO): Promise<User> {
return await this.userService.updateUser(updateUserDto);
}
This should do the work.
Let me know if it helps, otherwise don't hesitate to comment & share your findings ;)
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 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)
}
I am using Nest.Js with TypeORM and I want to hash my password before persisting into the DB.
I tried using the event decorator #BeforeInsert() however it wasn't working for me but later I found that it was not working because I was taking an DTO as an input.
user.controller.ts
#Post()
async create(#Body() data: CreateUserDto, #Res() res) {
// if user already exist
const isUserExist = await this.service.findByEmail(data.email);
if (isUserExist) {
throw new BadRequestException('Email already exist');
}
// insert user
this.service.create(data);
// send response
return res.status(201).json({
statusCode: 201,
message: 'User added Successfully',
});
}
user.service.ts
create(data: CreateUserDto) {
return this.userRepository.save(data)
}
So, I was basically using an DTO to save the data. That's why it was not working.
But what I want to do is map the DTO to user object. So, This is what I did.
#Post()
async create(#Body() data: CreateUserDto, #Res() res) {
// Create User object
const user = new User();
// Map DTO to User object
for (const [key, value] of Object.entries(data)) {
user[key] = value;
}
// if user already exist
const isUserExist = await this.service.findByEmail(user.email);
if (isUserExist) {
throw new BadRequestException('Email already exist');
}
// insert user
this.service.create(user);
// send response
return res.status(201).json({
statusCode: 201,
message: 'User added Successfully',
});
}
create-user.dto.ts
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '#nestjs/swagger';
export class CreateUserDto {
#IsNotEmpty()
#IsString()
#ApiProperty()
readonly firstName: string;
#IsNotEmpty()
#IsString()
#ApiProperty()
readonly lastName: string;
#IsNotEmpty()
#IsString()
#IsEmail()
#ApiProperty()
readonly email: string;
#IsNotEmpty()
#IsString()
#ApiProperty()
readonly password: string;
}
Is there any better approach for this? Because currently I have to write the code in every method to map it.
I would first move all the logic from my controller into the service. This would allow you to reuse the logic in other places, if there are any (since you prefer to have that service class).
Personally, I would avoid writing smart code because it saves me 2 or 3 lines of code. When someone else than you would have to review/refactor it would be a pain in the back. Just write something that's easy to understand.
Third, I would avoid using magic things like beforeInsert. Yeah, it might look smart but you don't make it clear how the pass is generated.
If your entity has the same fields as your DTO what's then the benefit of having the dto. Personally I would avoid exposing entity's password property. Instead, I would have a changePassword(generator: IUserPassGenerator) method in the entity. As for checking the pass, I would have somethingnlike verifyPass(validator: IPassChecker) method.
Another thing that I would avoid would be setters or public props mainly because it might cause your entity to enter into an invalid state. In your case e.g. someone else might change the password property with a md5 hash. After all, they can even change it with an unhashed string.
We can easily map Plain Object Literal to Class Instances by using 'class-transformer' package
Answer:
async create(#Body() data: CreateUserDto, #Res() res) {
const user = plainToClass(User, data)
}
this is a valid approach.
What you can do is extract this logic from the create method and create some kind of Builder object to create User objects from the DTO and vice-versa and call the builder where you need it.
I am trying to create simple appliaction with Nest.js, GraphQL and MongoDB. I wnated to use TypeORM and TypeGraphql to generate my schema and make a connection with localhost databasebut but i can not run my server with nest start becouse I am getting this error:
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL output type for getArticles
I have no idea why i am getting this error. My class ArticleEntity does't has any not primary types, so there should not be any problem. I tried to remove () => ID from #Field() decorator of filed _id of ArticleEntity class but it didn't helped
ArticleResolver
#Resolver(() => ArticleEntity)
export class ArticlesResolver {
constructor(
private readonly articlesService: ArticlesService) {}
#Query(() => String)
async hello(): Promise<string> {
return 'Hello world';
}
#Query(() => [ArticleEntity])
async getArticles(): Promise<ArticleEntity[]> {
return await this.articlesService.findAll();
}
}
ArticleService
#Injectable()
export class ArticlesService {
constructor(
#InjectRepository(ArticleEntity)
private readonly articleRepository: MongoRepository<ArticleEntity>,
) {}
async findAll(): Promise<ArticleEntity[]> {
return await this.articleRepository.find();
}
}
ArticleEntity
#Entity()
export class ArticleEntity {
#Field(() => ID)
#ObjectIdColumn()
_id: string;
#Field()
#Column()
title: string;
#Field()
#Column()
description: string;
}
ArticleDTO
#InputType()
export class CreateArticleDTO {
#Field()
readonly title: string;
#Field()
readonly description: string;
}
If you need anything else comment
ArticleEntity should be decorated with the #ObjectType decorator as shown in the docs https://typegraphql.com/docs/types-and-fields.html.
#Entity()
#ObjectType()
export class ArticleEntity {
...
}
For anyone who gets this error and uses enums, you may be missing a call to registerEnumType.
In my case, I was using the #ObjectType decorator, but I was importing from type-graphql. I imported from #nestjs/graphql instead, and the problem was resolved.
import { ObjectType } from '#nestjs/graphql';
See here for a related discussion on GitHub.
I was using MongoDB and I had my Query return the schema instead of the model class.
Changing #Query((returns) => UserSchema) to #Query((returns) => User) fixed the issue for me.
user.schema.ts
#ObjectType()
#Schema({ versionKey: `version` })
export class User {
#Field()
_id: string
#Prop({ required: true })
#Field()
email: string
#Prop({ required: true })
password: string
}
export const UserSchema = SchemaFactory.createForClass(User)
user.resolver.ts
#Query((returns) => User)
async user(): Promise<UserDocument> {
const newUser = new this.userModel({
id: ``,
email: `test#test.com`,
password: `abcdefg`,
})
return await newUser.save()
}
Output model class should be decorated with #ObjectType() and then all properties of that class will decorated with #Field().
For any one who is using a custom output model class and NOT an entity(sequelize, typeorm, prisma etc). Because I was using database entity first, everything was working fine till I moved to a more customized output model.
One more case would be someone using class A as output and class B is used within A
export class A{
id: number;
name:string;
childProperty: B
. . . . .
}
export class B{
prop1:string;
prop2:string;
}
In that case class B should also be decorated with #ObjectType and fields (prop1 , prop2 ) should be also be decorated with #Field as well.