title: NestJS/Mongoose: serialization does not exclude properties in plain output
body: <p>I've started to play with NestJS, migrating from my old express/mongoose project and immediately crashed into a fence, just following MongoDB/serializations chapters from NestJS docs. I've prepared following schema</p>
  <pre><code>/////// schema
  import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
  import * as mongoose from 'mongoose';
  import { Exclude, Expose } from 'class-transformer';
  
  export type UserDocument = User &amp; mongoose.Document;
  
  @Schema()
  export class User {
      @Prop()
      @Exclude()
      _id: String
  
      @Expose()
      get id(): String { return this._id ? `${this._id}` : undefined }
  
      @Prop()
      name: string
  
      @Prop({ unique: true })
      login: string
  
      @Exclude()
      @Prop()
      password: string        
  }
  
  export const UserSchema = SchemaFactory.createForClass(User);
  </code></pre>
  <p>registered it in app.module</p>
  <pre><code>MongooseModule.forRoot('mongodb://localhost/old_project'), 
  MongooseModule.forFeature([ { name: User.name, schema: UserSchema } ]),
  </code></pre>
  <p>and tried following calls, expecting no password property revealed in results</p>
  <pre><code>/////// controller
    @UseInterceptors(ClassSerializerInterceptor)
    @Get('default')
    async default(): Promise&lt;User&gt; {
      let u = new User();
      u.name = 'Kos';
      u.password = &quot;secret&quot;;
      u.login = 'k@o.s'
  
      return u;
    }
    
    // returns
    // {&quot;name&quot;:&quot;Kos&quot;,&quot;login&quot;:&quot;k@o.s&quot;}
  
    @Get('first_raw')
    async firstRaw(): Promise&lt;User&gt; {
      return this.userModel.findOne()
    }
    
    @Get('first_lean')
    async firstLean(): Promise&lt;User&gt; {
      return this.userModel.findOne().lean()
    }
    
    //both return
    // {&quot;_id&quot;:&quot;5f8731a36fc003421db08921&quot;,&quot;name&quot;:&quot;Kos&quot;,&quot;login&quot;:&quot;kos&quot;,&quot;password&quot;:&quot;secret&quot;,&quot;__v&quot;:0}
  
    @UseInterceptors(ClassSerializerInterceptor)
    @Get('first_raw_stripped')
    async firstRawStripped(): Promise&lt;User&gt; {
      return this.userModel.findOne()
    }
    
    //returns
    // {&quot;$__&quot;:{&quot;strictMode&quot;:true,&quot;selected&quot;:{},&quot;getters&quot;:{},&quot;_id&quot;:&quot;5f8731a36fc003421db08921&quot;,&quot;wasPopulated&quot;:false,&quot;activePaths&quot;:{&quot;paths&quot;:{&quot;_id&quot;:&quot;init&quot;,&quot;name&quot;:&quot;init&quot;,&quot;login&quot;:&quot;init&quot;,&quot;password&quot;:&quot;init&quot;,&quot;__v&quot;:&quot;init&quot;},&quot;states&quot;:{&quot;ignore&quot;:{},&quot;default&quot;:{},&quot;init&quot;:{&quot;_id&quot;:true,&quot;name&quot;:true,&quot;login&quot;:true,&quot;password&quot;:true,&quot;__v&quot;:true},&quot;modify&quot;:{},&quot;require&quot;:{}},&quot;stateNames&quot;:[&quot;require&quot;,&quot;modify&quot;,&quot;init&quot;,&quot;default&quot;,&quot;ignore&quot;]},&quot;pathsToScopes&quot;:{},&quot;cachedRequired&quot;:{},&quot;$setCalled&quot;:[],&quot;emitter&quot;:{&quot;_events&quot;:{},&quot;_eventsCount&quot;:0,&quot;_maxListeners&quot;:0},&quot;$options&quot;:{&quot;skipId&quot;:true,&quot;isNew&quot;:false,&quot;willInit&quot;:true,&quot;defaults&quot;:true}},&quot;isNew&quot;:false,&quot;$locals&quot;:{},&quot;$op&quot;:null,&quot;_doc&quot;:{&quot;_id&quot;:&quot;5f8731a36fc003421db08921&quot;,&quot;name&quot;:&quot;Kos&quot;,&quot;login&quot;:&quot;kos&quot;,&quot;password&quot;:&quot;secret&quot;,&quot;__v&quot;:0},&quot;$init&quot;:true}
  
    @UseInterceptors(ClassSerializerInterceptor)
    @Get('first_lean_stripped')
    async firstLeanStripped(): Promise&lt;User&gt; {
      return this.userModel.findOne().lean()
    }
    
    //returns
    // {&quot;_id&quot;:&quot;5f8731a36fc003421db08921&quot;,&quot;name&quot;:&quot;Kos&quot;,&quot;login&quot;:&quot;kos&quot;,&quot;password&quot;:&quot;secret&quot;,&quot;__v&quot;:0}
  </code></pre>
  <p>Finally I've found that only manual instantiation of User class does somehow what it should do, so I've added constructor to User class</p>
  <pre><code>constructor(partial?: Partial&lt;User&gt;) {
      if (partial)
          Object.assign(this, partial);
  }
  </code></pre>
  <p>and then it finally returned what was expected - no password prop in result</p>
  <pre><code>  @UseInterceptors(ClassSerializerInterceptor)
    @Get('first')
    async first(): Promise&lt;User&gt; {
      return new User(await this.userModel.findOne().lean());
    }
    
    //finally returns what's expected
    // {&quot;name&quot;:&quot;Kos&quot;,&quot;login&quot;:&quot;kos&quot;,&quot;__v&quot;:0,&quot;id&quot;:&quot;5f8731a36fc003421db08921&quot;}
  </code></pre>
  <p>Am I missing something? Somehow it seems a bit overwhelming...</p>
  <p><strong>UPDATE:</strong> it is either question about NestJS mongoose and serialization coupling - why this</p>
  <pre><code>  @UseInterceptors(ClassSerializerInterceptor)
    @Get('first')
    async first(): Promise&lt;User&gt; {
      return await this.userModel.findOne().lean();
    }
  </code></pre>
  <p>doesn't work and this</p>
  <pre><code>  @UseInterceptors(ClassSerializerInterceptor)
    @Get('first')
    async first(): Promise&lt;User&gt; {
      return new User(await this.userModel.findOne().lean());
    }
  </code></pre>
  <p>works (which also means for each result enumerable map with entity creations required)</p>