4

Does such a thing exist or do I follow standard Mongoose procedure?

I read the docs, I spent the whole day yesterday for this, but I could only find relative ones that placed the functions inside the service component. This is not effective as if I would like to use a static model function outside of the service component (say, a custom decorator), it wouldn't reach it as DI is private.

I would have created an Issue on Github for documentation request, but I feared I may have overlooked something.

Edit 2: Please do not change the title of the post. "Nest" is not a typo for "best". It is referring to a Node.js framework called Nest.js. (See post tags and referenced documentation link)

Edit: In the MongoDB section of the docs, there's this piece of code:

constructor(@InjectModel(CatSchema) private readonly catModel: Model<Cat>) {}

but specifically, this Model<Cat> part, imports Cat from an interface that extends Mongoose Document interface. Wouldn't it be better if this Cat interface was a class instead which was capable of functions (even after transpilation)?

msamprz
  • 573
  • 2
  • 7
  • 15

2 Answers2

1

I use the following approach:

When defining the schema, add static methods to the Mongoose schema:

UserSchema.methods.comparePassword = async function(candidatePassword: string) {
  return await bcrypt.compare(candidatePassword, this.password);
};

Also include the method in the object's interface definition:

export interface User {
  firstName: string;
  ...
  comparePassword(candidatePassword: string): Promise<boolean>;
}

as well as the UserDocument interface

export interface UserDocument extends User, Document { }

So now my UsersService:

export class UsersService {
  constructor(@InjectModel(Schemas.User) private readonly userRepository: Model<UserDocument>,
              private readonly walletService: WalletsService,
              @Inject(Modules.Logger) private readonly logger: Logger) {}

  async findByEmail(email: string): Promise<UserDocument> {
    return await this.userRepository.findOne({ email }).select('password');
  }
  ...
}

And to tie it all together, when a user tries to log in, the Auth service retrieves a user object by id, and invokes that user object's instance method of comparePassword:

@Injectable()
export class AuthService {
  constructor(
    private readonly usersService: UsersService,
    private readonly jwtService: JwtService,
  ) { }

  async signIn({ email, password }: SignInDto): Promise<LoginResponse> {
    const user = await this.usersService.findByEmail(email);
    if (!user) { throw new UnauthorizedException('Invalid Username or Password'); }

    if (await user.comparePassword(password)) {
      const tokenPayload: JwtPayload = { userId: user.id };
      const token = this.jwtService.sign(tokenPayload);
      return ({ token, userId: user.id, status: LoginStatus.success });
    } else {
      throw new UnauthorizedException('Invalid Username or Password');
    }
  }
}
pantera
  • 163
  • 1
  • 10
0

@InjectModel() is a helper decorator to inject registered component. You can always use a model class directly instead of injecting it through a constructor. Thanks to that you can use a model everywhere (but I'm not sure whether a custom decorator is a right choice). Also, Model<Cat> is redundant here. You can replace this type with anything else that fits your use-case, for example typeof X if you want to call static functions.

Kamil Myśliwiec
  • 8,548
  • 2
  • 34
  • 33
  • So I wasn't able to deal with the static functions matter, but I moved the functions I needed from being mongoose static functions to being service functions, then injecting the services in my other module. I used [this](https://docs.nestjs.com/modules) in the docs to do it, basically exporting the service in one module and importing in the other, then injected in the service and used the functions of the first service. Thank you for the reply by the way. – msamprz Feb 06 '18 at 07:06
  • 1
    Glad that you have found solution :) – Kamil Myśliwiec Feb 06 '18 at 09:45