Search code examples
typescriptnestjsinterceptor

Cannon serialize response data in Nest.js


I have endpoint where user can sign up. It returns promise with User generic type, but, because of fact, that it returns User created instance, there are a couple of fields that I'd like to remove, for example password.

@Post('/sign-up')
signUp(@Body() signUpUserDto: SignUpUserDto): Promise<User> {
  return this.userService.signUp(signUpUserDto);
}

I tried 2 ways.

First one

So, according to documentation, I used @UseInterceptors(ClassSerializerInterceptor) interceptor and class-transform @Exclude decorator, but it doesn't work (neither decorator is put on DTO or User)

@UseInterceptors(ClassSerializerInterceptor)
@Post('/sign-up')
signUp(@Body() signUpUserDto: SignUpUserDto): Promise<User> {
  return this.userService.signUp(signUpUserDto);
}

DTO:

import { Exclude } from "class-transformer";

export class SignUpUserDto {
  readonly email: string;

  @Exclude()
  readonly password: string;

Or model:

@Table
export class User extends Model<User, IUserCreatingAttributes> {
  @PrimaryKey
  @Default(DataType.UUIDV4)
  @Column({ type: DataType.UUID })
  id: string;

  @Column({ type: DataType.STRING, allowNull: false, unique: true })
  email: string;

  @Exclude()
  @Column({ type: DataType.STRING, allowNull: false })
  password: string;

Second one

Actually, I tried to do the same, but with custom interceptor. Had no result also:

import { instanceToPlain } from 'class-transformer';

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response> {
  intercept(
    context: ExecutionContext,
    next: CallHandler
  ): Observable<Response> {
    return next.handle().pipe(
      map((data) => ({
        statusCode: context.switchToHttp().getResponse().statusCode,
        message: data.message || '',
        data: instanceToPlain(data)
      }))
    );
  }
}

I have a guess that it doesn't work, because I return promise, and not instance, but I am not sure, maybe I just do something wrong.

So, what is the problem? How can I serialize this data?

EDIT.

I use Sequelize as ORM, if it is important.

EDIT 2.

This is how signUp function looks like:

constructor(
    @InjectModel(User) private userRepository: typeof User,
    @InjectModel(UserBan) private userBanRepository: typeof UserBan,
    @Inject(forwardRef(() => AuthService))
    private authService: AuthService,
    @Inject(forwardRef(() => RoleService))
    private roleService: RoleService
  ) {}
...
 async signUp(signUpUserDto: SignUpUserDto): Promise<User> {
    const sameEmailUsernameUser = await this.userRepository.findOne({
      where: {
        [Op.or]: [
          { email: signUpUserDto.email },
          { username: signUpUserDto.username }
        ]
      }
    });

    if (sameEmailUsernameUser)
      throw new HttpException('user-already-exists', HttpStatus.BAD_REQUEST);

    const hashedPassword = await bcrypt.hash(signUpUserDto.password, 5);
    const user = await this.userRepository.create({
      ...signUpUserDto,
      password: hashedPassword
    });
    let role = await this.roleService.getRole({ value: 'USER' });

    /** Instead of seeder */
    if (!role) {
      role = await this.roleService.createRole({
        value: 'USER',
        description: 'Common user'
      });
    }

    await user.$set('roles', [role.id]);
    user.roles = [role];

    return user;
  }

Solution

  • I have found other solution, I can not @Exclude fields that I don't want in response, but @Expose fields, that I need.

    import { Exclude, Expose } from "class-transformer";
    
    interface IUserCreatingAttributes {
      email: string;
      password: string;
    }
    
    @Exclude()
    @Table
    export class User extends Model<User, IUserCreatingAttributes> {
      @PrimaryKey
      @Default(DataType.UUIDV4)
      @Column({ type: DataType.UUID })
      @Expose()
      id: string;
    
      @Column({ type: DataType.STRING, allowNull: false, unique: true })
      @Expose()
      email: string;
    
      @Column({ type: DataType.STRING, allowNull: false })
      password: string;
    
      @Column({ type: DataType.STRING, allowNull: false, unique: true })
      @Expose()
      username: string;
     ...
    

    And don't forget about @UseInterceptors(ClassSerializerInterceptor).