I am using Firestore to build a social media app, I am wondering what is the best way to structure the data for friend requests is.
When a user John wants to friend request Mary and Anne, Do I create a root collection called friend_requests
and add 3 documents:
- A document with John’s
userId
as a key the value of which will be an object each for both Mary and Anne’s containing theiruserId
and thestatus
of request (pending/accepted/declined) - A document with Mary’s
userId
as a key and an array with an object with John’suserId
andstatus
of request - A document with Annes’s
userId
as a key and an array with an object with John’suserId
andstatus
of request
Now, when Anne and Mary accept the friend request, I delete all three documents in friend_requests
collection and then:
- I add a subcollection for each user (in
users
root collection) calledfriends
that contains usersIds, displayNames, and photoURLs of friends that have accepted requests
or
- Should I make another root collection
friends
that has a document for each user containing the friends information instead?
It seems like a lot of duplicate data so I’m trying to wrap my head around it and find the best and most efficient way to do it in Firestore. Any advice would be much appreciated and maybe I am going about it the wrong way!
2
Answers
The first approach seems to have a lot of document creations and deletions. Instead you could create a sub-collection "friends" under "users" and store a field "status". So the structure would be:
When User2 sends a friend request to User1, you can add a friend sub-document under User1 and set the
status
field torequested
along withfriendId
. Once User1 accepts the request, just update the status toactive
. This will reduce costs of that 1 delete and 1 create operations per friend request.This should cover many use cases including:
You can use Firestore triggers for Cloud Functions if you need to process anything like updating database, sending a notification/e-mail, etc when the friend request has been accepted.
The
friends
sub-collection could be a root-level collection but in that case you’ll have to add another fielduserId
to filter of friends of a given user. That doesn’t make a big difference but checkout What are the benefits of using a root collection in Firestore vs. a subcollection? for more information on that.While Dharmaraj’s solution will certainly work, I think that there is an even simpler solution for that, that can help you reduce the number of reads a little bit. So let’s assume
userOne
sends a friend request touserTwo
, anduserThree
sends a friend request to theuserOne
.To have a flattened schema, instead of creating nested collections or nested maps under the User object, I have created a separate top-level collection called
requests
. Inside this collection, there will be documents that will manage the user’s own requests, friend requests, and all friends.Seeing this schema, you might think, oh, but how can I manage to add such an amount of data in a single document? Even if we’re limited to storing up to 1 Mib of storage in a single document, when it comes to storing such simple objects, we can store pretty much. However, if you’re worried about the space, then you should consider creating separate collections for each situation,
ownRequests
,friendRequests
, andallFriends
. In this case, for simplicity, let’s assume all the data fits into a single document.Going forward, as you can see, we have stored the minimum data from a User object under each map, where the key is the UID and the value is represented by three fields. In this way you can display the name and the profile photo, to clearly indicate the user who performed the friend request.
Now, when
userOne
sends a friend request touserTwo
, there are two operations that need to be done. The first one would be to adduserOne
inside thefriendRequests
map ofuserTwo
and the second one would be to adduserTwo
under its ownownRequests
map.Between these two friends we have now a friend request, this means that you’ll always have to make sure that you’ll restrict
userTwo
from sending friend requests touserOne
. How can you do that? Simply by checking the existence of the$userTwoUid
insideownRequests
ofuserOne
. If you want theuserOne
to be able to cancel the friend request, oruserTwo
to reject the friend request, then simply revert the above two operations.The same rule applies in the case
userThree
.Regarding displaying data, if you want to show a list of your own requests, friend requests, or a list of all friends, you need to make a single database call, that costs a single read. If you however need to load more data of a user, then when you click on the user, you can perform another request and load all the data of the user, since you already know the UID.
When
userTwo
accepts the friend request fromuserOne
, simply move their objects insideallFriends
map. You can also go ahead and add abannerFriends
if you want, to prevent some other users from sending friend requests. Please also note, that this will also cost only a single read.Denormalizing data it’s a quite common practice when it comes to NoSQL databases like Firestore and for that I recommend you check my answer from the following post:
Where you’ll also be able to see other solutions for structuring such a database.