klabe
klabe

Reputation: 505

Typescript Mongoose ignore certain field in query result the type safe way

I am using Nestjs and Mongoose. I need to fetch a user profile but ignore the password from Mongodb.

My user schema looks like this

@Schema()
export class User extends Document {
  @Prop({ required: true, unique: true })
  email: string;

  @Prop({required: true})
  password: string;
}

When the user logs in, I need to fetch the user with the password so that I can authenticate the user. Here is the findOne method in UsersService. It uses User the schema as the type

async findOne(filter: FilterQuery<User>): Promise<User> {
    return this.userModel.findOne(filter).exec();
}

However, in my authentication method, after the user is authenticated, I need to return the user without the password.

async validateUser(email: string, password: string): Promise<User | null> {
    const user = await this.usersService.findOne({ email });
    if (user && await compare(password, user.password)) {
      const {password: ignore, ...result} = user.toObject();
      return result;
    }

    return null;
  }

This works, but user.toObject() returns type any which will ignore the type check. I still would like validateUser method to make sure I return a promise of User or null

Is there a type safe way to do so? Thank you very much in advance!

Upvotes: 1

Views: 1904

Answers (1)

Rafi Henig
Rafi Henig

Reputation: 6432

In order to transform your plain JavaScript object returned by toObject into a typed object, you might want to use class-transformer library (the library is recommended by Nest.js for validation purposes).

Step 1.

First install class-validator:

$ npm i --save class-transformer

Step 2.

Once installed, assuming User class has the following properties:

 class User {
     public id: any;
     public email: string;
     public password: number;
 }

Create antoher class named UserWithoutPassward and import Exclude

import { Exclude } from "class-transformer";

class UserWithoutPassword extends User {
    @Exclude()
    public password: number
}

We use Exclude annotation to let class-transformer know we would like to exclude password.

Step 3.

import the plainToClass function:

import { plainToClass } from "class-transformer";

And back to your code:

async validateUser(email: string, password: string): Promise<UserWithoutPassword | null> {
    const user = await this.usersService.findOne({ email });

    if (user && await compare(password, user.password))
    {
        return plainToClass(UserWithoutPassword, user.toObject());
    }

    return null;
}

plainToClass method transforms a plain javascript object to instance of specific class, accepting the following parameters:

  1. The class to instantiate
  2. plain object

Further reference about the library

Hope it helps.

Upvotes: 2

Related Questions