1
votes

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.

3

3 Answers

2
votes

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.

  1. 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.

  2. 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.

0
votes

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.

0
votes

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)

}