How to remove Field Name in custom message in class-validator NestJS - nestjs

I have problems with regards to creating custom messages using the class-validator. It always returns the field name.
export class EditOrderAddressDto implements Partial<CreateOrderAddressDto> {
#IsOptional()
#IsString()
#MinLength(1, { message: 'Last Name should at least have more than 1 character.'})
lastName?: string;
}
export class EditOrderDto implements EditOrderDtoModel {
#ApiProperty({ required: false })
#IsOptional()
#ValidateNested()
#Type(() => EditOrderAddressDto)
billingAddress?: Partial<CreateOrderAddressDto>;
}
Then for my payload I just pass this.
{ deliveryAddress:
{ lastName: "" }
}
And I get this as the error message "deliveryAddress.Last Name should at least have more than 1 character.'
Is there any way I can remove the deliveryAddress and return only the custom message?

Related

class-transformer #Type() decorator doesn't work

I have a dto file:
export class UpdateUserDto {
#IsUUID()
public readonly uuid: string;
#IsObject()
#Type(() => UserModelDto)
public readonly dataToUpdate: UserModelDto;
}
The problem is, it seems #Type() decorator doesn't work. My UserModelDto looks like this:
export class UserModelDto {
#IsUUID()
#IsOptional()
public uuid?: string;
#IsEmail()
#IsOptional()
public email?: string;
#IsString()
#IsOptional()
public password?: string;
#IsJWT()
#IsOptional()
public refreshToken?: string;
}
When I send a request to a controller validation doesn't work for dataToUpdate field however for uuid it does. I've tried many ways but result remains the same.
You need to enable { transform: true } inside the ValidationPipe options:
app.useGlobalPipes(
new ValidationPipe({
transform: true,
}),
);
reference: https://docs.nestjs.com/techniques/validation#transform-payload-objects
To ensure errors on validation when extra properties are sent in, you need to make use of the forbidNonWhitelisted option in the ValidaitonPipe. If you just want to strip the values you can use transform: true and whitelist: true
You have forgot to add #validateNested decorator.

Two validators for one single entity in DTO

Are there any ways or would it be possible to have two validator in one single entity? Like for the given example code below, the identifier would accept an email as its payload but it would also accept
number/mobile number as its payload as well.
#ApiProperty()
#IsString()
#IsNotEmpty()
#IsEmail()
identifier: string;
EDIT:
I have tried,
#ApiProperty()
#IsString()
#IsNotEmpty()
#IsEmail()
#IsPhoneNumber('US')
identifier: string;
But it does not work.
EDIT 2:
I found a reference code based on this previous thread, How to use else condition in validationif decorator nestjs class-validator?, and I copied his validation class.
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from "class-validator";
import { IdentifierType } from "../interface/access.interface";
#ValidatorConstraint({ name: 'IdentifierValidation', async: false })
export class IdentifierValidation implements ValidatorConstraintInterface {
validate(identifier: string, args: ValidationArguments) {
if (JSON.parse(JSON.stringify(args.object)).type === IdentifierType.MOBILE) {
var regexp = new RegExp('/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im');
// "regexp" variable now validate phone number.
return regexp.test(identifier);
} else {
regexp = new RegExp("^[a-zA-Z0-9_.+-]+#[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$");
// "regexp" variable now validate email address.
return regexp.test(identifier);
}
}
defaultMessage(args: ValidationArguments) {
if (JSON.parse(JSON.stringify(args.object)).type === IdentifierType.MOBILE) {
return 'Enter a valid phone number.'
} else {
return 'Enter a valid email address.'
}
}
}
DTO -
export class VerifyOtpDto {
#Validate(IdentifierValidation)
#ApiProperty()
#IsNotEmpty()
identifier: string;
#ApiProperty({ enum: IdentifierType })
#IsNotEmpty()
identifierType: IdentifierType;
}
ENUM -
export enum IdentifierType {
EMAIL = 'email',
MOBILE = 'mobile',
}
It does work with email but trying to feed a mobile number still does not work.
You have two ways to do this, first with regex:
#Matches(/YOUR_REGEX/, {message: 'identifier should be email or phone'})
identifier: string;
Or you can get the idea from this:
#IsType(Array<(val: any) => boolean>)
#IsType([
val => typeof val == 'string',
val => typeof val == 'boolean',
])
private readonly foo: boolean | string;
Of course it can get more than one validator in one DTO column.
Did you check https://www.npmjs.com/package/class-validator here?
if you want to check mobile number, you can use to #IsMobilePhone(locale: string).

Transform the #Body without requiring it in NestJs

What I basically want to do is to parse the date string from the request to a Date object like in this question.
However, this is not my use case because in my case the date is not required. So if I use the solution from the question above it responds with a 400: due must be a Date instance.
This is my DTO:
export class CreateTaskDto {
#IsDefined()
#IsString()
readonly name: string;
#IsDefined()
#IsBoolean()
readonly done: boolean;
#Type(() => Date)
#IsDate()
readonly due: Date;
}
Then in my controller:
#Post('tasks')
async create(
#Body(new ValidationPipe({transform: true}))
createTaskDto: CreateTaskDto
): Promise<TaskResponse> {
const task = await this.taskService.create(createTaskDto);
return this.taskService.fromDb(task);
}
Post request with this payload is working fine:
{
"name":"task 1",
"done":false,
"due": "2021-07-13T17:30:11.517Z"
}
This request however fails:
{
"name":"task 2",
"done":false
}
{
"statusCode":400
"message":["due must be a Date instance"],
"error":"Bad Request"
}
Is it somehow possible to tell nestjs to ignore transformation if there is no date?
#IsOptional()
Checks if given value is empty (=== null, === undefined) and if so, ignores all the validators on the property.
https://github.com/typestack/class-validator#validation-decorators
#Type(() => Date)
#IsDate()
#IsOptional()
readonly due?: Date;

TypeORM getRawOne<T> not returning type T

I'm working on refactoring a koa api to nest and am kinda stuck on refactoring the queries from native psql to typeorm. I have the following table, view and dto.
#Entity()
export class Challenge {
#PrimaryGeneratedColumn()
id!: number;
#Column()
endDate!: Date;
#CreateDateColumn()
createdAt!: Date;
}
#ViewEntity({
expression: (connection: Connection) => connection.createQueryBuilder()
.select('SUM(cp.points)', 'score')
.addSelect('cp.challenge', 'challengeId')
.addSelect('cp.user', 'userId')
.addSelect('RANK() OVER (PARTITION BY cp."challengeId" ORDER BY SUM(cp.points) DESC) AS rank')
.from(ChallengePoint, 'cp')
.groupBy('cp.challenge')
.addGroupBy('cp.user')
})
export class ChallengeRank {
#ViewColumn()
score!: number;
#ViewColumn()
rank!: number;
#ViewColumn()
challenge!: Challenge;
#ViewColumn()
user!: User;
}
export class ChallengeResultReponseDto {
#ApiProperty()
id!: number;
#ApiProperty()
endDate!: Date;
#ApiProperty()
createdAt!: Date;
#ApiProperty()
score: number;
#ApiProperty()
rank: number;
test() {
console.log("test")
}
}
As the object I want to return is not of any entity type, I'm kinda lost on how to select it and return the correct class. I tried the following:
this.challengeRepository.createQueryBuilder('c')
.select('c.id', 'id')
.addSelect('c.endDate', 'endDate')
.addSelect('c.createdAt', 'createdAt')
.addSelect('cr.score', 'score')
.addSelect('cr.rank', 'rank')
.leftJoin(ChallengeRank, 'cr', 'c.id = cr."challengeId" AND cr."userId" = :userId', { userId })
.where('c.id = :id', { id })
.getRawOne<ChallengeResultReponseDto>();
Which returns an object that has the correct fields, but that is not of the class type "ChallengeResultReponseDto". If I try to call the function "test" the application crashes. Further it feels weird to use the challengeRepository but not return a challenge, should I use the connection or entity manager for this instead?
I'm rather certain that getRawOne<T>() returns a JSON that looks like whatever you give the generic (T), but an not instance of that class. You should try using getOne() instead to get the instance of the returned entity

NestJS - Creating Business Objects From Other Services

Nowadays I'm playing around with NestJS and trying to understand the best practices of NestJS world.
By following official documentations I have created service/dto/entity/controller for my business object called 'Cat'.
(also using SequelizeJS)
cat.entity.ts
#Table({
freezeTableName: true,
})
export class Cat extends Model<Cat> {
#AllowNull(false)
#Column
name: string;
#Column
breed: string;
}
create-cat.dto.ts
export class CreateCatDto {
#IsString()
readonly name: string;
#IsString()
readonly breed: string;
}
cat.service.ts
export class CatService {
constructor(#Inject('CAT_REPOSITORY') private readonly CAT_REPOSITORY: typeof Cat) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const cat = new Cat();
cat.name = createCatDto.name;
cat.breed = createCatDto.breed;
return await cat.save();
}
}
Now I can make POST requests to my controller and create Cat objects successfully. But I want to create a 'Cat' from other service and the create() method only takes CreateCatDto which I cannot/shouldn't initialize (it's read-only).
How can I call create(createCatDto: CreateCatDto) from other services? How you are handling this kind of requirements with NestJS? Should I create one more method like createFromEntity(cat: Cat) and use it?
try this:
const cat: CreateCatDto = { name: 'Miau', breed: 'some' };

Resources