3

I am trying to structure a firebase database for chat system. What I wanted to achieve is after user successfully logged in, they will see a list of message they have sent to different users. Each of the message preview will show the last message. Then, user can select on the message to view the full chat details. It should work like the Facebook Messenger.

My design structure as such:

chatMessage
    sender *(Assume this one is user)*
        threads
            threadID1
                messageID1
                    datetime, content, receiver, status
                messageID2
                    datetime, content, receiver, status
            threadID2
                messageID1
                    datetime, content, receiver, status
                messageID2
                    datetime, content, receiver, status
    sender *(Assume this one is admin)*
         threads
            threadID1
                messageID1
                    datetime, content, receiver, status
                messageID2
                    datetime, content, receiver, status

The design above allows me to know let's say userID1 logged in, I can retrieve all the messages he sent. However, I am not able to know if there is any reply prior to the message and therefore I am not able to retrieve the last message.

How can I actually restructure it so that I can achieve what I mentioned above? Any suggestions?

Thanks!

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
QWERTY
  • 2,303
  • 9
  • 44
  • 85

1 Answers1

11

It sounds like you want to:

  1. Have chat "rooms" between users
  2. Show a list of each user's chat rooms, with the latest message for that room

If those are your requirements, I'd model precisely those in your database.

So for each chat room (a chat between a certain set of users), model the messages for that room:

chats: {
  $roomId: {
    $messageId: {
      senderId: "..."
      message: "..."
    }
  }
}

Now for each user, model a separate list of their chats and the latest message:

userRooms: {
  $uid: {
    $roomId: {
      "message: "..."
    }
  }
}

Now whenever a user posts a message to a room, you will need to push that message to /chats/$roomId and for each user in that chat room write the message to /userRooms/$uid/$roomId (overwriting the exiting message there).

This type of data duplication is known as fanning out data, because you're spreading a single snippet of information over multiple places in the database. It is quite common in NoSQL databases, and is part of the reason they scale so well: they trade write complexity for read performance.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Hey thanks so much! But just to double confirm on my understanding, /chats/$roomId basically stores all the messages from all users for certain 'room'. Let's say person A send message to admin, and admin reply to person A, all these should store under the same $roomId. Then for /userRooms/$uid/$roomId, this is to ease the retrieving of all 'rooms' by certain user in order to list them out after user logged in. Is my understanding correct? – QWERTY Dec 19 '17 at 06:57
  • 3
    @hyperfkcb This is an awesome answer! To expand a tad; this structure does show the latest message, it doesn't include who it was from or perhaps the timestamp. A simple change is that instead of duplicating the message text in userRooms, just store a reference to which message was the latest: *userRooms/$uid/$roomid/$messageId = true*. Then when the user logs in the app can read that node which will retrieve which specific message was the latest. Then read the message node for additional information. Yeah, it's two reads but adds a little flexibility depending on what data is needed. :-) – Jay Dec 19 '17 at 13:31
  • Thanks for the additions Jay! That's indeed the other option: only duplicate the key of the latest message. I prefer duplicating the actual data needed, since it removes the need for an extra read. But as said: that is a preference, not a recommendation. :-) – Frank van Puffelen Dec 19 '17 at 14:06
  • @Jay Ahh I see I see. Let me confirm with you again for my understanding, so you mean update the $messageId under userRooms/$uid/$roomid/ every time when a new message was send from the specific chat room? So basically by getting the messageId, I can simply retrieve all other info like timestamp etc that comes along with the message itself instead of just a message text? – QWERTY Dec 19 '17 at 14:12
  • @hyperfkcb Yup - you got it! If you just need the message text then Frank's answer will everything you need. If you want more data than just the message, then you can keep a reference to message via it's messageId. – Jay Dec 19 '17 at 18:03
  • I see I see! Thanks so much to both of you!! :) – QWERTY Dec 20 '17 at 01:31
  • @FrankvanPuffelen Hey just a quick check with you, I was thinking how can I uniquely generate the $roomId. Because whenever I post a new message, it will generate a unique ID for the message itself but not the room. – QWERTY Dec 26 '17 at 03:03
  • Because I am trying to do something like person A talks to admin A and vice versa and each room has only a maximum of two persons – QWERTY Dec 26 '17 at 03:25
  • One of my classic answers show how you can generate the room ID based on its participants, meaning that the same participants always automatically end up in the same room. See http://stackoverflow.com/questions/33540479/best-way-to-manage-chat-channels-in-firebase – Frank van Puffelen Dec 26 '17 at 04:33
  • @FrankvanPuffelen Hi Frank, I'm looking to use your method of structuring my data like you've said above, however I'm a bit unsure how to go about updating the last message of the other chat user. I thought about the possibility of using a cloud function, however that would be a lot of them being fired for every message. However if I was to update both users chat records, then this would probably raise a security risk. Any ideas? Thanks – juicy89 Sep 12 '18 at 10:01