Change dto field value in nestjs - nestjs

I'm new in nest js. I want to do a transformation for incoming request:
import { IsPhoneNumber } from 'class-validator';
import { Transform } from 'class-transformer';
export class PhoneLoginDto {
#Transform(({ value }) => value.replace(/^0/, '+98'))
#IsPhoneNumber()
phoneNumber: string;
}
But this is not working, should it be done here in dto?
Thank you in advance.

I just needed to pass transform:true in main.ts:
app.useGlobalPipes(new ValidationPipe({ transform: true }));

Related

Class-Validator (Node.js) Get another property value within custom validation

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.

class-transformer does not work : Transform function is not called

I try to use class-transformer but i can't do it.
I also use type-graphql and #typegoose/typegoose
Here's my code:
My Decorator
import { Transform } from 'class-transformer';
export function Trim() {
console.log('DECORATOR');
return Transform(({ value }: { value: string }) => {
console.log('value: ', value);
return value.trim();
});
}
My InputType
import { IsEmail } from 'class-validator';
import { InputType, Field } from 'type-graphql';
import { User } from '../Entities/User';
import { Trim } from '../../Decorators/Sanitize';
#InputType({ description: 'Input for user creation' })
export class AddUserInput implements Partial<User> {
#Field()
#Trim()
#IsEmail({ domain_specific_validation: true, allow_utf8_local_part: false })
email!: string;
}
My Resolver
import { Arg, Mutation, Resolver } from 'type-graphql';
import { User } from '../Entities/User';
import { AddUserInput } from '../Types/UsersInputs';
#Resolver(() => User)
export class UserResolvers {
#Mutation(() => String, { description: 'Register an admin' })
async createAccount(#Arg('data') data: AddUserInput): Promise<string> {
console.log({ ...data });
return data.email;
}
}
My Entity
import { prop, getModelForClass } from '#typegoose/typegoose';
import { ObjectType, Field } from 'type-graphql';
#ObjectType({ description: 'User model' })
export class User {
#Field({ description: 'The user email' })
#prop({ required: true, unique: true, match: [/\S+#\S+\.\S+/, 'is invalid'] })
email!: string;
}
export const UserModel = getModelForClass(User, {
schemaOptions: { timestamps: true }
});
My Request
POST http://localhost:5000/app HTTP/1.1
Content-Type: application/json
X-REQUEST-TYPE: GraphQL
mutation
{
createAccount (data : {
email: " email#gmail.com ",
})
}
The problem is that the console.log('value: ', value) inside Transform function is never call and my email is not trim.
Also console.log('DECORATOR') is not call when I do the request but just one time when server starting.
Thanks !
Typegoose transpiles classes into mongoose schemas & models, it does not apply any validation / transformation aside from mongoose provided ones, so your class-transformer decorators would only be called when using the class directly and using its functions. (like plainToClass and classToPlain)
In your case, it would be better to either use PropOptions get & set or pre-hooks.
As a note, typegoose provides a guide for using class-transformer with typegoose classes Integration Example: class-transformer, just to show that it can be used like normal classes.
Also note, it is currently recommended against using class-transformer because of mentioned issues inside the documentation.

Is there a way to hide all the end-point in the controller.ts using a single decorator?

Currently, I am using #ApiExcludeEndpoint() ### on top of all methods to hide the end-point in the swagger-ui, like this:
import { Controller, Get, Query, Param } from '#nestjs/common';
import { ResourceService } from './resource.service';
import { Auth } from 'src/auth/auth.decorator';
import {
ApiTags,
ApiSecurity,
ApiOkResponse,
ApiForbiddenResponse,
ApiCreatedResponse,
ApiExcludeEndpoint
} from '#nestjs/swagger';
#Controller()
#ApiTags('Resources')
#ApiSecurity('apiKey')
export class ResourceController {
constructor(private readonly resourceService: ResourceService) {}
#Get('get_url')
#ApiExcludeEndpoint()
#Get()
#ApiOkResponse({
description: 'Resources list has succesfully been returned',
})
#ApiForbiddenResponse({ description: 'You are not allowed' })
#Auth(...common_privileges)
findAll(#Query() query: any): any {
......
}
#Get('get_url/:id')
#ApiExcludeEndpoint()
#ApiOkResponse({ description: 'Resource has succesfully been returned' })
#ApiForbiddenResponse({ description: 'You are not allowed' })
#Auth(...common_privileges)
findById(#Param('id') id: string, #Query() query: any): any {
......
}
}
I Need to know is there a way to hide all the end-point in the controller using a single decorator, I checked some documents it says to use #ApiIgnore() and #Hidden() but I can't find those in nestjs-swagger. Please comment on this
One possibility is to explicitly include the modules that you'd like to include in the swagger docs instead of just "including all modules" by default. Example:
const options = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const catDocument = SwaggerModule.createDocument(app, options, {
include: [LionsModule, TigersModule], // don't include, say, BearsModule
});
SwaggerModule.setup('api/cats', app, catDocument);
Without the explicit include:[] property, LionsModule, TigersModule, and BearsModule would be automatically included.
To hide all the end-point in the controller.ts, you must use ApiExcludeController instead of ApiExcludeEndpoint as in the example.
https://docs.nestjs.com/openapi/decorators
import { Controller, Get, Query, Param } from '#nestjs/common';
import { ResourceService } from './resource.service';
import { Auth } from 'src/auth/auth.decorator';
import {
ApiTags,
ApiSecurity,
ApiOkResponse,
ApiForbiddenResponse,
ApiCreatedResponse,
ApiExcludeController
// ApiExcludeEndpoint
} from '#nestjs/swagger';
#Controller()
#ApiTags('Resources')
#ApiSecurity('apiKey')
#ApiExcludeController()
export class ResourceController {
constructor(private readonly resourceService: ResourceService) {}
#Get('get_url')
// #ApiExcludeEndpoint()
#Get()
#ApiOkResponse({
description: 'Resources list has succesfully been returned',
})
#ApiForbiddenResponse({ description: 'You are not allowed' })
#Auth(...common_privileges)
findAll(#Query() query: any): any {
......
}
#Get('get_url/:id')
// #ApiExcludeEndpoint()
#ApiOkResponse({ description: 'Resource has succesfully been returned' })
#ApiForbiddenResponse({ description: 'You are not allowed' })
#Auth(...common_privileges)
findById(#Param('id') id: string, #Query() query: any): any {
......
}
}

Now to enable validators DTO in NEST JS

Im new in NEST JS, and now im try to include some validator in DTO'S
looks like:
// /blog-backend/src/blog/dto/create-post.dto.ts
import { IsEmail, IsNotEmpty, IsDefined } from 'class-validator';
export class CreatePostDTO {
#IsDefined()
#IsNotEmpty()
title: string;
#IsDefined()
#IsNotEmpty()
description: string;
#IsDefined()
#IsNotEmpty()
body: string;
#IsEmail()
#IsNotEmpty()
author: string;
#IsDefined()
#IsNotEmpty()
datePosted: string;
}
But when i excute the post service like:
{
"title":"juanita"
}
Its return good!
But the validators should show and error rigth?
My post controloler
#Post('/post')
async addPost(#Res() res, #Body() createPostDTO: CreatePostDTO) {
console.log(createPostDTO)
const newPost = await this.blogService.addPost(createPostDTO);
return res.status(HttpStatus.OK).json({
message: 'Post has been submitted successfully!',
post: newPost,
});
}
My main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(5000);
}
bootstrap();
Let's binding ValidationPipe at the application level, thus ensuring all endpoints are protected from receiving incorrect data. Nestjs document
Enable ValidationPipe for your application.
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '#nestjs/common'; // import built-in ValidationPipe
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe()); // enable ValidationPipe`
await app.listen(5000);
}
bootstrap();
For validating requests through the incoming dtos, you can use the #UsePipes() decorator provided by NestJS. This decorator can be applied globally (for the complete project), on individual endpoints. This is what the NestJS documentation says about it -
Pipes, similar to exception filters, can be method-scoped, controller-scoped, or global-scoped. Additionally, a pipe can be param-scoped. In the example below, we'll directly tie the pipe instance to the route param #Body() decorator.
Thus using it for your POST endpoint will help validate the request.
Hope this helps.
You also can register global pipes in app.module.ts file like this:
providers: [
{
provide: APP_PIPE,
useValue: new ValidationPipe({
// validation options
whitelist: true,
}),
},
],

Pass #IsInt() validation for application/x-www-form-urlencoded request type

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;

Resources