Modelling a Chat like Application in Firebase
Asked Answered
S

5

11

I have a Firebase database structuring question. My scenario is close to a chat application. Here are the specifics

- users(node storing several users of the app)
  - id1
      name: John
  - id2
      name: Meg
  - id2
      name: Kelly
- messages(node storing messages between two users)
  - message1
      from: id1
      to: id2
      text: ''
  - message2
      from: id3
      to: id1
      text: ''

Now imagine building a conversations view for an individual user. So I want to fetch all messages from that particular user and to that particular user

I am writing it as follows right now:

let fromMessagesRef = firebase.database().ref('messages').orderByChild('from').equalTo(firebase.auth().currentUser.uid)
fromMessagesRef.once("value").then((snapshot) => {/* do something here*/})

let toMessagesRef = firebase.database().ref('messages').orderByChild('to').equalTo(firebase.auth().currentUser.uid)
toMessagesRef.once("value").then((snapshot) => {/* do something here*/})

Questions:

  1. Is this the right way to model the problem?
  2. If yes, is there a way to combine the above 2 queries?
Stewartstewed answered 1/9, 2016 at 2:11 Comment(0)
P
10

I would store the data like this:

- users(node storing several users of the app)
  - id1
      name: John
      messages
        message1: true
        message2: true
  - id2
      name: Meg
      messages
        message1: true
        message3: true
  - id3
      name: Kelly
      messages
        message2: true
        message3:true
- messages(node storing messages between two users)
  - message1
      from: id1
      to: id2
      text: ''
  - message2
      from: id3
      to: id1
      text: ''
  - message3
      from: id2
      to: id3
      text: ''

Firebase recommends storing things like this. So in your case your query would be

let fromMessagesRef = firebase.database().child('users').child(firebase.auth().currentUser.uid).child('messages')

This allows it to be very fast as there is no orderBy being done. Then you would loop over each message and get it's profile from the messages node.

Pronator answered 8/9, 2016 at 13:53 Comment(4)
To clarify, is Kelly meant to be id3?Sideways
Ha ha.. yeah. It was a copy/paste from the question.Pronator
So does this return all of the messages for a user and not necessarily just the messages between the two users in a specific conversation? eg If message3 was between id2 and id3 then would id2 still see message1 when they ran this query? It seems like you would see more than just the messages between the two current users?Sideways
In that case you can run two queries, one where the from is id2 and the to is id3 and one where the from is d3 and the to is id2. If you want to just run one query you'll have to store the data like that. So under each user you would have another key to a different user and then it's keys would be the messages that the two users share.Pronator
S
3

The structure you have is one possible way to model this data. If you're building an application like this, I would highly recommend the angularfire-slack tutorial. One potentially faster way to model the data would be to model the data like is suggested in this tutorial https://thinkster.io/angularfire-slack-tutorial#creating-direct-messages

{
  "userMessages": {
    "user:id1": {
      "user:id2": {
        "messageId1": {
          "from": "simplelogin:1",
          "body": "Hello!",
          "timestamp": Firebase.ServerValue.TIMESTAMP
        },
        "messageId2": {
          "from": "simplelogin:2",
          "body": "Hey!",
          "timestamp": Firebase.ServerValue.TIMESTAMP
        }
      }
    }
  }
}

The one thing you need to watch for in this case if you choose to do it like this is that before your query, you need to sort which user will be the "primary user" under whom the messages will be stored. As long as you make sure that is the same every time, you should be good to go.

One improvement you could make to this structure is something you already pointed out - flattening your data and moving the messages to another node - like you did in your example.

To answer your second question, if you were to keep that structure, I think you would need both of those queries, because firebase does not support a more complicated OR query that would allow you to search both at the same time.

Sideways answered 7/9, 2016 at 2:44 Comment(0)
G
2

No. Firebase Auth subsystem is where you want to store the email, displayName, password, and photoURL for each user. The function below is how you do it for a password-based user. oAuth-based users are easier. If you have other properties you want to store, like age for example, put those under a users node with each users uid that Firebase Authentication provides you.

  function registerPasswordUser(email,displayName,password,photoURL){
    var user = null;
    //NULLIFY EMPTY ARGUMENTS
    for (var i = 0; i < arguments.length; i++) {
      arguments[i] = arguments[i] ? arguments[i] : null;
    }
    auth.createUserWithEmailAndPassword(email, password)
    .then(function () {
      user = auth.currentUser;
      user.sendEmailVerification();
    })
    .then(function () {
      user.updateProfile({
        displayName: displayName,
        photoURL: photoURL
      });
    })
    .catch(function(error) {
      console.log(error.message);
    });
    console.log('Validation link was sent to ' + email + '.');
  }

As for the messages node, get the random id from Firebase Realtime Database's push method and use that as the id of each message under messages. Firebase queries are used:

var messages = firebase.database().ref('messages');
var messages-from-user = messages.orderByChild('from').equalTo('<your users uid>');
var messages-to-user = messages.orderByChild('to').equalTo('<your users uid>');

messages-from-user.once('value', function(snapshot) {
  console.log('A message from <your users uid> does '+(snapshot.exists()?'':'not ')+' exist')
});

messages-to-user.once('value', function(snapshot) {
  console.log('A message to <your users uid> does '+(snapshot.exists()?'':'not ')+' exist')
});

Define an index for messages-from-user and messages-to-user in your Rules:

{
  "rules": {
    "messages": {
      ".indexOn": ["from", "to"] 
    }
  }
}
Greenhaw answered 10/9, 2016 at 3:8 Comment(1)
Since Firebase Authentication doesn't have a public API to retrieve a list of users, it is quite common to also store specific properties from the user's profiles in the database.Filum
T
2

Below data structure gives you more flexibility with you data. Instead of having to store each messages that user sent back and forth I would suggest to store it in separate node and store the messageID with each user that is involved in the conversation.

Obviously you need to set the security rules, so other user can't see conversation if they are not in the conversation.

By doing this we are not creating deep chain node inside user info

   - users(node storing several users of the app)
      - id1
          name: John
          messages: [msID1, msID9]
      - id2
          name: Meg
          messages: [msID1, msID7]
      - id3
          name: Kelly
           messages: [msID9, msID7]

    - messages(node storing messages between two users)
      - msID1
          from: id1
          to: id2
          text: ''
      - msID7
          from: id3
          to: id2
          text: ''
       - msID9
          from: id3 
          to: id1
          text: ''
Tizes answered 12/9, 2016 at 15:26 Comment(0)
K
1

Firebase has actually built a demo (and extendible) chat application called Firechat. The source and documentation is provided, and of particular note is the section on their data structures.

Although they've implemented chatrooms, you can see that they've flattened their data structures as in many of the other answers. You can read more about how and why this is done in the Firebase guide.

Kapoor answered 13/9, 2016 at 2:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.