John Doah
John Doah

Reputation: 1999

How would you model a collection of users and friends in Firebase?

I'm trying to create a database (json) with Firebase. I searched the docs and the net but couldn't find a clear way to start. I want to have a database of users. each user (represented as UID) should have a nickname and a list of friends.

I tried making a .json file that looks like this:

{
  users:{

  }
}

and adding it to the Firebase console to get started but it wouldn't work. How can I do it?

the database should look like this:

{
  users:{
       UID:{
          nickname: hello
          friends: UID2
       }
       UID2:{
          nickname: world
          friends: UID
  }
}

I don't know if I got that right, so I would really appreciate any help you guys could give me at this subject. Thanks in advance!

Upvotes: 2

Views: 2099

Answers (2)

MikkelT
MikkelT

Reputation: 815

I keep a collection of Friends where the users field is an array of 2 user ids: ['user1', 'user2'].

Getting the friends of a user is easy:

friendsCollection.where("users", "array-contains", "user1").get()

This should get you all documents where user1 appears.

Now the tricky part was on how to query a single friend. Ideally, firebase would support multiple values in array-contains, but they won't do that: https://github.com/firebase/firebase-js-sdk/issues/1169

So they way I get around this is to normalize the users list before adding the document. Basically I'm utilizing JS' truthiness to check what userId is greater, and which is smaller, and then making a list in that order.

when adding a friend:

const user1 = sentBy > sentTo ? sentBy : sentTo
const user2 = sentBy > sentTo ? sentTo : sentBy

const friends = { users: [user1, user2] }

await friendsCollection.add(friends)

This basically ensures that whoever is part of the friendship will always be listed in the same order, so when querying, you can just:

await friendsCollection.where("users", "==", [user1, user2]).get()

This obviously only works because I trust the list will always have 2 items, and trust that the JS truthiness will work deterministically, but it's a great solution for this specific problem.

Upvotes: 0

Frank van Puffelen
Frank van Puffelen

Reputation: 599641

Seems like a good place to start. I would make two changes though.

  1. keep the list is friends separate
  2. keep the friends as a set, instead of a single value or array

keep the list is friends separate

A basic recommendation when using the Firebase Database is to keep your data structure shallow/flat. There are many reasons for this, and you have at least two.

  1. With your current data structure, say that you want to show a list of user names. You can only get that list by listening to /users. And that means you don't just get the user name for each user, but also their list of friends. Chances that you're going to show all that data to the user are minimal, so that means that you've just wasted some of their bandwidth.

  2. Say that you want to allow everyone to read the list of user names. But you only want each user to be able to read their own list of friends. Your current data structure makes that hard, since permission cascades and rules are not filters.

A better structure is to keep the list of user profiles (currently just their name) separate from the list of friends for each user.

keep the friends as a set

You current have just a single value for the friends property. As you start building the app you will need to store multiple friends. The most common is to then store an array or list of UIDS:

[ UID1, UID2, UID3 ]

Or

{
  "-K.......1": "UID1"
  "-K.......5": "UID2"
  "-K.......9": "UID3"
}

These are unfortunately the wrong type for this data structure. Both the array and the second collection are lists: an ordered collection of (potentially) non-unique values. But a collection of friends doesn't have to be ordered, it has to be unique. I'm either in the collection or I'm not in there, I can't be in there multiple times and the order typically doesn't matter. That's why you often end up looking for friends.contains("UID1") or ref.orderByValue().equalTo("UID1") operations with the above models.

A much better model is to store the data as a set. A set is a collection of unordered values, which have to be unique. Perfect for a collection of friends. To store that in Firebase, we use the UID as the key of the collection. And since we can't store a key without a value, we use true as the dummy value.

So this leads to this data model:

{
  users:{
     UID:{
       nickname: hello
     }
     UID2:{
       nickname: world
     }
  }
  friends:{
     UID:{
       UID2: true
     }
     UID2:{
       UID: true
     }
  }
}

There is a lot more to say/learn about NoSQL data modeling in general and Firebase specifically. To learn about that, I recommend reading NoSQL data modeling and watching Firebase for SQL developers.

Upvotes: 11

Related Questions