node.jsmongodbexpressmongoosemongodb-atlas

How to push item to a heavily nested array in MongoDB using mongoose in JavaScript


I'm trying to build a messaging site using expressJS and reactJS. I'm currently working on the message-sending functionality of the site. My goal is that when the user clicks the send button on the react page, it makes a post request to the express backend, which pushes the message data to the correct user's "messages" array in my MongoDB Altlas collection. The problem is that I can't figure out how to push the message to the right user's array because it is nested with other data in the collection. Can anyone help me?

Here is my index.js express code that I'm stuck on:

const userSchema = new mongoose.Schema({
    name: String,
    password: String,
    profilePicture: String,
    contacts: [{name: String, 
        profilePicture: String, 
        messages: [{sender: String, message: String}], 
    }]
});
const User = new mongoose.model('User', userSchema);


app.post("/send-message", (req, res) => {
    var sender = req.body.sender;
    var recv = req.body.recv;
    var message = req.body.msg
    User.findOne({name: sender}).then(user => {
        user.contacts.forEach(contact => {
            if (contact.name === recv){
                // newMessage is the object I would like to push to the 
                // messages array for the sender.
                var newMessage = {sender: "me", message: message};
            }
        });
    }).then(() => {
        User.findOne({name: recv}).then(user => {
            user.contacts.forEach(contact => {
                if (contact.name === sender){
                    // newMessage is the object I would like to push to 
                    // the messages array for the receiver.
                    var newMessage = {sender: "them", message: message};
                }
            });
        }).then(res.send("Message Sent!"))
        .catch(e => {console.log("2: " + e); res.send("Error!")});
    }).catch(e => {console.log("1: " + e); res.send("Error!")});
});

Here is my React code for the SendMessage function:

const [usrn, setUsrn] = React.useState("<Username>");
const [pass, setPass] = React.useState("<Password>");
const [messageThread, setMessageThread] = React.useState({});
const [message, setMessage] = React.useState("");

const SendMessage = () => {
        axios.post(props.apiUrl + "send-message/", {sender: usrn, recv: 
                   messageThread.contact, msg: message})
        .then(res => {
            console.log(res.data);
            setMessage("");
        });
    }

Solution

  • You can use findOneAndUpdate to select the User and $push the new message object to the correct array. By selecting contacts.name you can use the mongodb $ positional operator which will only push to the matching array element.

    This example uses the async/await pattern with try/catch for easier error handling. You had multiple then.catch blocks which is a downward spiral in my experience.

    app.post("/send-message", async (req, res) => { //< Mark callback as async
        try{
            const sender = req.body.sender;
            const recv = req.body.recv;
            const message = req.body.msg
            // Add message to sender
            const userSender = await User.findOneAndUpdate({ //< use await pattern
                name: sender,
                'contacts.name': recv
            },{
                $push:{
                    'contacts.$.messages': { sender: "me", message: message }    
                }
            }, {new: true});
            if(!userSender){ //< If no sender was found return error
                return res.status(400).json({
                    message: 'Sender not found'
                })
            }
            // Add message to receiver
            const userReceiver = await User.findOneAndUpdate({ //< use await pattern
                name: recv,
                'contacts.name': sender
            },{
                $push:{
                    'contacts.$.messages': { sender: "them", message: message }
                }
            }, {new: true});
            if(!userReceiver){ //< If no receiver was found return error
                return res.status(400).json({
                    message: 'Receiver not found'
                })
                // Handle deletion of object in sender
            }
            return res.status(201).json({
                message: 'Messages Saved!'
            })
        }catch(err){
            console.log(err);
            return res.status(500).json({
                message: 'Error on server.'
            })
        }
    });