Search code examples
node.jstypescriptmongodbmongoose

TS[2339]: Property not exist on type '() => Promise<(Document<unknown, {}, IUser> & Omit<IUser & { _id: ObjectId; }, never>) | null>'


I've defined the user schema using following:

import mongoose from 'mongoose'
import { Model, Schema } from 'mongoose'
import bcrypt from 'bcrypt'

enum LoginType {
  EMAIL = 'email',
  GOOGLE = 'google',
}

interface IUser extends mongoose.Document {
  username: string
  email: string
  password: string
  loginTypes: LoginType
  comparePassword(password: string, callback: Function): Promise<Function>
}

interface IUserMethods extends Model<IUser> {
  comparePassword(password: string, callback: Function): Promise<Function>
}

type UserModel = Model<IUser, {}, IUserMethods>;

const userSchema = new Schema<IUser, UserModel, IUserMethods>({
  username: {
    type: String,
    required: true,
  },
  email: {
    type: String,
    required: true,
  },
  password: {
    type: String,
    require: true,
  },
  loginTypes: {
    type: String,
    require: true,
    enum: Object.values(LoginType),
  },
})

/* callback = (err: Error, isMatch: any) => {} */
userSchema.methods.comparePassword = async function (
  password: string,
  callback: Function
): Promise<Function> {
  let result
  try {
    result = await bcrypt.compare(password, this.password)
    return callback(null, result)
  } catch (err) {
    return callback(err, result)
  }
}

export default mongoose.model<IUser>('User', userSchema)

And calling it in the login route, but typescript shows an error Property 'comparePassword' does not exist on type '() => Promise<(Document<unknown, {}, IUser> & Omit<IUser & { _id: ObjectId; }, never>) | null>'. on foundUser.comparePassword(req.body.password, (err: Error, isMatch: any) => {})

Following is my login route:

router.post('/credentials', (req, res) => {
  /* Check if user exists in database */
  const foundUser = async () => await User.findOne({ email: req.body.email })

  if (!foundUser) {
    res.status(401).send({ message: 'User does not exist' })
  }

  /* Check if password matches */
  foundUser.comparePassword(req.body.password, (err: Error, isMatch: any) => {})

  const jwt_token = jwt.sign(
    { name: req.body.name, email: req.body.email, id: req.body.id },
    JWT_SECRET,
    { expiresIn: '10000' }
  )
  res.status(200).send({
    name: req.body.name,
    email: req.body.email,
    id: req.body.id,
    jwt_token: jwt_token,
  })
})

I've search for it and try this solution before but not working.
While, I found

const a = new User({})
a.comparePassword(req.body.password, (err: Error, isMatch: any) => {})

will work fine just like what the mongoose docs said but not working if I use findOne before calling comparePassword


Solution

  • On this line:

    const foundUser = async () => await User.findOne({ email: req.body.email })
    

    You are defining a function called foundUser that when called will return the result of await User.findOne({ email: req.body.email }).

    As written, you would need to call the function foundUser to actually query MongoDB. Something like:

    const foundUser = async () => await User.findOne({ email: req.body.email })
    const user = await foundUser();
    

    But I think what you intended is something like this:

    const foundUser = await User.findOne({ email: req.body.email })
    

    Here foundUser is not a function, it is the result of the MongoDB query.