NestJS mongoose return model when i use ClassSerializerInterceptor

Viewed 414

I've started to play with NestJS and TypeScript.

I made a simple application that returns the user from MongoDB by ID on a GET request. And the response to client should not have some fields such as password

But if i use .lean() i get response with _id === {}:

{
  "_id": {},
  "firstName": "sdfsdf",
  "lastName": "dfgdfg5y",
  "time": 234234
}

If I don't use the lean(), I get mongoose model object:

{
  "$__": {
    "activePaths": {
      "paths": {
        "_id": "init",
        "firstName": "init",
        "lastName": "init",
        "password": "init",
        "time": "init"
      },
      "states": {
        "ignore": {},
        "default": {},
        "init": {
          "_id": true,
          "firstName": true,
          "lastName": true,
          "password": true,
          "time": true
        },
        "modify": {},
        "require": {}
      },
      "stateNames": [
        "require",
        "modify",
        "init",
        "default",
        "ignore"
      ]
    },
    "strictMode": true,
    "skipId": true,
    "selected": {},
    "fields": {},
    "exclude": null,
    "_id": {}
  },
  "$isNew": false,
  "_doc": {
    "_id": {},
    "firstName": "sdfsdf",
    "lastName": "dfgdfg5y",
    "password": "some pass",
    "time": 234234
  }
}

Iterestingly, the console.log() in the user.controller.ts prints the object I need to the console:

{
  _id: new ObjectId("6245e409e06ac89e5f03851b"),
  firstName: 'sdfsdf',
  lastName: 'dfgdfg5y',
  password: 'some pass',
  time: 234234
}

I expect to get an object with _id and an excluded password

user.module.ts

import { User, UserSchema } from './schemas/user.schema';
import { MongooseModule } from '@nestjs/mongoose';
import { UserService } from './user.service';
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';

@Module({
  imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
  controllers: [UserController],
  providers: [UserService]
})
export class UserModule {}

user.controller.ts

import { UserService } from './user.service';
import { UserDto } from './dto/user.dto';
import { ClassSerializerInterceptor, Controller, UseInterceptors, Get, Param } from '@nestjs/common';

@Controller('user')
@UseInterceptors(ClassSerializerInterceptor)
export class UserController {

   constructor(private readonly service: UserService) {}

   @Get('/:id')
   async getOne(@Param('id') id: string): Promise<UserDto> {
      const user = await this.service.findOne(id);
      console.log(user); // <===============================================
      return new UserDto(user);
   }
}

user.service.ts

import { UserDto } from './dto/user.dto';
import { User, UserDocument } from './schemas/user.schema';
import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';

@Injectable()
export class UserService {
   constructor(@InjectModel(User.name) private UserModel: Model<UserDocument>) {}

   async findOne(id: string): Promise<UserDto> {
      return await this.UserModel.findById(id).lean();
   }
}

user.dto.ts

import { Exclude, Expose } from 'class-transformer';

export class UserDto {
  @Expose()
  _id: string;
  firstName: string;
  lastName: string;
  time: number;

  @Exclude()
  password: string;

  constructor(partial: Partial<UserDto>) {
    Object.assign(this, partial);
  }
}

user.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type UserDocument = User & Document;

@Schema()
export class User {
   @Prop()
   firstName: string;

   @Prop()
   lastName: string;

   @Prop()
   password: string;

   @Prop()
   time: number;
}

export const UserSchema = SchemaFactory.createForClass(User);
I'm starting to hate it a little bit
1 Answers

Remove:

@Exclude()

Then:

@Injectable()
export class InterceptorForClassSerializer extends ClassSerializerInterceptor {
  serialize(response: any, options: ClassTransformOptions) {
    const rawDataJSON = JSON.stringify(response);
    return super.serialize(JSON.parse(rawDataJSON), options);
  }
}

@SerializeOptions({
  excludePrefixes: ['password']
})
@UseInterceptors(InterceptorForClassSerializer)
Related