Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose Private Chat Message Model

I'm trying to add private messaging between users into my data model. I've been going back and forth between two possible ways of doing this.

1) Each user has an array of user_id, chat_id pairs which correspond to chats they are participating in. Chat model just stores chat_id and array of messages.

2) Don't store chats with user at all and just have the Chat model store a pair of user_ids and array of messages.

The issue with option (1) is whenever a user joins or starts a chat, I would need to look first through the array for the user to see if the user_id, chat_id pair already exists. And then do a second find for the chat_id in Chat. If it doesn't exist, I would need to create the user_id, chat_id pair in two different places for both users who are participating.

With option (2) I would search through the Chat model for the user_id1, user_id2 pair, and if I find it I'm done, if not I would create a new Chat record for that pair and done.

Based on this option (2) does seem like the better way of handling this. However, I'm running into issues figuring out how to model the "pair" of user ids in a way that they are easily searchable in the chat model. i.e. how do I make sure I can find the chat record even if the user_ids are passed in the wrong order, i.e. user_id2, user_id1. What would be the best way to model this in Mongoose?

var chatSchema = mongoose.Schema({

  messages: [{
        text: { 
          type: String,
          max: 2000
        },
        sender: { 
          type: mongoose.Schema.Types.ObjectId, 
          ref: 'User'
        }
      }],
  participant1: [{                
          type: mongoose.Schema.Types.ObjectId, 
          ref: 'User'
        }]
  participant2: [{                
          type: mongoose.Schema.Types.ObjectId, 
          ref: 'User'
        }]
});

If it's something like above, how would I search for a participant pair? Could I order the participant IDs in some way so that they are always participant1 < participant2 for example, making search simpler?

like image 344
Praneeth Wanigasekera Avatar asked Nov 14 '14 18:11

Praneeth Wanigasekera


2 Answers

Well, there is no correct answer to this question, But definitely, the approaches you have mentioned are not the best at all!

Firstly, when you are thinking about designing a "chat" model, you need to take into account that there would be millions of messages between the users, so you need to care about performance when you want to fetch the chats.

Storing the messages into an array is not a good idea at all, your model's size will be large by the time and you have to consider that MongoDB's document size limit is currently 16 MB per document.

https://docs.mongodb.com/manual/reference/limits/

Secondly, You have to consider pagination aspect because it will affect the performance when the chat is large, when you retrieve the chat between 2 users you won't request all the chats since the beginning of the time, you will just request the most recent ones, and then you can request the older ones if the user scroll the chat, this aspect is very important and can't be neglected due to its effect on performance.

My approach will be to store each message in a separated document

First of all, storing each message in a single document will boost your performance during fetching the chats, and the document size will be very small.

This is a very simple example, you need to change the model according to your needs, it is just to represent the idea:

const MessageSchema = mongoose.Schema({
    message:{
        text: { type:String, required:true }
        // you can add any other properties to the message here.
        // for example, the message can be an image ! so you need to tweak this a little
    }
    // if you want to make a group chat, you can have more than 2 users in this array
    users:[{
        user: { type:mongoose.Schema.Types.ObjectId, ref:'User', required:true }
    }]
    sender: { type:mongoose.Schema.Types.ObjectId, ref:'User', required:true },
    read: { type:Date }
},
{
    timestamps: true
});

you can fetch the chats by this query:

 Message.find(({ users: { "$in" : [#user1#,#user2#]} })
    .sort({ updatedAt: -1 })
    .limit(20)

Easy and clean! as you see, pagination becomes very easy with this approach.

like image 138
Maysara Alhindi Avatar answered Oct 06 '22 23:10

Maysara Alhindi


A few suggestions.

First - why store Participant1 and 2 as arrays? There is one specific sender, and one (or more) recipients (depending on if you want group messages).

Consider the following Schema:

var ChatSchema = new Schema({
    sender : {
        type : mongoose.Schema.Types.ObjectId,
        ref : 'User'
    },
    messages : [
        {
            message : String,
            meta : [
                {
                    user : {
                        type : mongoose.Schema.Types.ObjectId,
                        ref : 'User'
                    },
                    delivered : Boolean,
                    read : Boolean
                }
            ]
        }
    ],
    is_group_message : { type : Boolean, default : false },
    participants : [
        {
            user :  {
                type : mongoose.Schema.Types.ObjectId,
                ref : 'User'
            },
            delivered : Boolean,
            read : Boolean,
            last_seen : Date
        }
    ]
});

This schema allows one chat document to store all messages, all participants, and all statuses related to each message and each participant.

the Boolean is_group_message is just a shorter way to filter which are direct / group messages, maybe for client side viewing or server-side processing. Direct messages are obviously easier to work with query-wise, but both are pretty simple.

the meta array lists the delivered/read status, etc, for each participant of a single message. If we weren't handling group messages, this wouldn't need to be an array, but we are, so that's fine.

the delivered and read properties on the main document (not the meta subdocument) are also just shorthand ways of telling if the last message was delivered/read or not. They're updated on each write to the document.

This schema allows us to store everything about a chat in one document. Even group chats.

like image 44
Augie Gardner Avatar answered Oct 06 '22 23:10

Augie Gardner