'How to use User.findOne({ userID: searchUserID } correctly ??? (currently not getting return value)

I am currently trying to implement an update / put functionality in my node.js-project and having issues I have absolutely no clue, where that comes from. (also I am a JS beginner). I am using express, MongoDB and mongoose.

Problem is in this method (or how I call it):

User.findOne({ userID: searchUserID })

Kontext of the the method, that is called:

/* find User by userID */ 
// calling sequence: UserService > findUserBy() callback

    function findUserBy(searchUserID, callback) {
        logger.debug(`UserService: searching for user with userID '${searchUserID}'...`)
        let query = User.findOne({ userID: searchUserID }) 
        query.exec(function (err, user) {/*content*/})
    }

When I call it directly from the UserRoute module like this:

/* get one user */
// calling sequence: UserRoute > router.get('/:userID') callback > userService.findUserBy() callback

    router.get('/:userID', function (req, res, next) {
        userService.findUserBy(req.params.userID, function (err, user) {/* content */})
    })

then it all works fine.

But when I want to use that function from another function inside the same module (UserService):

/* update one User */
// calling sequence: UserService > updateUser > findUserBy() callback

    function updateUser(userID, newUserData, callback) {
        findUserBy(userID, function (err, user) {...})
        let id = user.body.id
        /* more content */
    }  

Then I am getting an error, since 'user' is not defined.

Obviously User.findOne({ userID: searchUserID }) is working in the first case and returns a user. But it is not working in the second case.

I have used the debugger to check the parameters, that are delivered when calling the findOne function.

The 1st parameter is: searchUserID
and the value is: 'manfred'

The 2nd parameter is a callback function, but it doesn't come this far anyway.

The first parameter is exactly the same, no matter whether I call it directly from UserRoute.js or the UserService.js.

I have also compared the content of the 'query'-objects in both cases. It has a bit more then 3700 lines and they have differences in like 10 lines... some veeeery long numbers that I don't understand. Probably random Numbers, timeStamps or ...I really have no clue (but if someone needs them I can look them up again).

Why is User.findOne({ userID: searchUserID }) not working in the second case?

EDIT: my full functions (without shortening them with 'content)

in UserService.js:

//find User by userID
function findUserBy(searchUserID, callback) { //UserService > findUserBy() callback
    logger.debug(`UserService: searching for user with userID '${searchUserID}'...`)
    let query = User.findOne({ userID: searchUserID }) // query object erstellen
    query.exec(function (err, user) { //query wird asynchron ausgeführt
        if (err) {
            logger.error(err.message)
            return callback(err.message)  // callback übergibt fehlernachricht
        }
        if (user) {  // hier wirkt null wie false
            logger.debug(`Found userID: ${searchUserID}`)
            callback(null, user)
        }
        else {
            //logger.error("Did not find user for userID: " + searchUserID)
            callback(`Did not find user with userID: ${searchUserID}`, user)  // callback übergibt fehlernachricht
        };
    })
}

This one is not done yet, but that comes after:

    // update User
    function updateUser(userID, newUserData, callback) {
        logger.debug(`Updating user '${userID}'...`)
        findUserBy(userID, function (err, user) { //UserService>updateUser>findUserBy() callback
            if (user) {
                logger.debug(user)
                    (null, user.body)
            }
            else {
                logger.error(err)
                return console.log("Did not find any User with this userID" + [], null)
            }
        })
        //logger.debug("user:" + user, err)
        console.log("so far so good...")
        let id = user.body.id
        let user = new User()
        Object.assign(user, user.body)
        Object.assign(user, newUserData)
        user.body.id = id
        user.save(function (err) {
            if (err) {
                logger.debug("Could not create user account: " + err)
                return callback("Could not create user account", null)
            }
            else {
                callback(null, user)
            }
        })
    }

in UserRoute.js:

/* update one User */ 
    router.put('/:userID', function (req, res, next) {
        userService.updateUser(req.params.userID, req, function (err, user) {
            if (user) {
                //res.send(Object.assign(user))
                logger.debug(user)
                res.send(`User ${req.body.userID} sucessfully updated. \r\r new Json-Body: \r ` + user)
            }
            else {
                logger.error(err)
                res.send("Did not find any User with this userID" + [])
            }
        })
    })


/* get one user */
router.get('/:userID', function (req, res, next) {
    userService.findUserBy(req.params.userID, function (err, user) { //UserRoute > router.get('/:userID') callback > userService.findUserBy() callback
        if (user) {
            res.send(user)
            logger.debug(user)
            //res.json(user)
        }
        else {
            logger.error(err)
            res.send("Did not find any User with this userID" + [])
        }
    })
})


Solution 1:[1]

you need to convert req.params.userID to an object Id For eg :

Types.ObjectId(req.params.uuid)

also Import Types

import { Types } from 'mongoose'

Solution 2:[2]

It does not work because you try to access the data while you still fetching it from the DB.

My opinion: Dont use callbacks, they are pretty old and you will sooner or later land into an callback hell. Mongoose supports Promises. Rewrite your code a bit:

function findUserBy(searchUserID) {
    logger.debug(`UserService: searching for user with userID '${searchUserID}'...`)
    return User.findOne({ userID: searchUserID }).exec()
}

If you not provide an callback, your function will return an promise with either null or your user.

Lets rewrite your router a bit:

router.get('/:userID', async function (req, res, next) {
   let user = await userService.findUserBy(req.params.userID)
   console.log(user)
})

I made an async / await out of it. await only works in an async function and can await Promises.

If you do this now:

async function updateUser(userID, newUserData) {
    let user = await findUserBy(userID)
    if(!user) {
       console.log("no user found")
    }
    let id = user.body.id
    /* more content */
}  

You should see some result. I made updateUser an async function. So you can use await. But watch out. Async function returns an promise, so you will also need to use await on them.

If you ever see an callback as second argument, then its most of the time asynchronouse code.

Edited code:

function findUserBy(searchUserID) {
  return User.findOne({ userID: searchUserID }).exec(); // query object erstellen
}

async function updateUser(userID, newUserData) {
  let user = await findUserBy(userID);
  if (!user) throw "Cannot find user";
  let newUser = new User({ ...user.body, ...newUserData });
  return newUser.save();
}

/* update one User */

router.put("/:userID", async function(req, res, next) {
  try {
    let user = await userService.updateUser(req.params.userID, req.body);
    res.status(200).json(user);
  } catch (err) {
    res.status(404).json("Did not find any User with this userID" + []);
  }
});

/* get one user */
router.get("/:userID", async function(req, res, next) {
  try {
    let user = await userService.findUserBy(req.params.userID);
    if (!user)
      return res
        .status(404)
        .send("Did not find any User with this userID" + []);
    return res.status(200).json(user);
  } catch (err) {
    return res.status(500).json(err);
  }
});

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ayush
Solution 2