Firebase Data Structure Best Practice for User Interaction

Firebase Data Structure Best Practice For User Interaction

This is very difficult to give one answer because there are 100 different structures that would work. Here's a cross-linked, double referenced structure to illustrate that point

movies
movie_0
title: "Monty Python and the Holy Grail"
reviewed_by
uid_1: tre
reviews
review_0: true

reviews
review_0
movie_id: "movie_0"
uid: "uid_1"
review: "Best movie evah"

users
uid_0
name: "Tim"
reviewed_movies:
movie_0: true
reviews
review_0: true

With that crazyness, you can:

query movies for which users reviewed it or which reviews
query reviews for certain users, certain movies and that the actual review was
query users for names, which movies that user reviewed and what the reviews were

So the question is; what do you need to query for?

Lets take your #3

When it's time to display say all a user's reviews, do I then query
the database using the user's ID?

The the structure I presented, if you want to get all of a users reviews, you don't have to query at all! You directly observe that users node

let ref = fbRef.child("users").child("uid_0").child("reviews")
ref.observe...

then you can iterate over the results to obtain the review references

Likewise, you could query the reviews node for all reviews where uid is the uid of the the user in question.

I realize this isn't a specific answer but it does provide guides

EDIT

One of the comments was a specific question so let me address it this way.

Suppose the only goal is to see all of the reviews for a specific movie. The structure could be greatly simplified:

movies
movie_0
title: "Monty Python and the Holy Grail"
reviews
review_0
movie_id: "movie_0"
uid: "uid_1"
review: "Best movie evah"
users
uid_1
name: "Tim"

Then suppose we know the movie and the movie id of movie_0. To get the reviews and the user, something like this will work

let reviewsRef = self.ref.child("reviews")
let query = reviewsRef.queryOrdered(byChild: "movie_id").queryEqual(toValue: "movie_0")
query.observeSingleEvent(of: .value) { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let uid = dict["uid"] as! String
let review = dict["review"] as! String

let userRef = self.ref.child("users").child(uid)
userRef.observeSingleEvent(of: .value, with: { userSnapshot in
let userDict = userSnapshot.value as! [String: Any]
let userName = userDict["name"] as! String
print("\(userName) said \(review)")
})
}
}

Best way to structure my firebase database

Working with NoSql Data , your need to take care of few things:-

  • Avoid Nesting The Data Too Deep
  • Flatten your dataStructure as possible
  • Prefer Dictionaries
  • Security Rules [Firebase]

Try this structure:-

users:{
userID1: {..//Users info..
posts:{
postID1: true,
postID2: true,
postID3: true,
}
},
userID2: {..//Users info..},
userID3: {..//Users info..},
userID4: {..//Users info..},
},
posts: {
userID1 :{
postID1: {..//POST CONTENT },
postID2: {..//POST CONTENT },
postID3: {..//POST CONTENT },
}

}

What are some good ways to structure data in firebase?

I'll go ahead and leave an answer for how I would approach this. My answer will be geared more towards Firestore even though the question is marked as Realtime Database. There are multiple ways to structure the data. This is the general structure I would use given your example:

users
- name
- timestamp

posts
- imageURL
- description
- timestamp
- likeCount
- commentCount

posts/comments //subcollection
- userID
- comment
- timestamp

posts/likes //subcollection
- userID
- timestamp

savedposts
- postID
- userID

followers
- userID
- followedID

Some additional notes:

Image Upload

The best option here is to upload the images to cloud storage and utilize a cloud function to generate a public URL and save it to the post document.

Comment / User Search

As stated in my comment, Firebase does not have a great solution for text based searches. The solution I utilized in my project was to utilize a cloud function to keep an Algolia index in sync with my users collection. I then offload the user search to them through a callable cloud function - though you could utilize the Algolia client SDK directly in your app if you wanted. In your scenario, you would also have to keep all of your comments in sync as well. Algolia isn't a cheap service, so I would look into the pros / cons of using the other options listed in the docs.

Document IDs

I generally let Firestore auto ID the documents, but here I would make some exceptions. For the savedposts and followers collections I would utilize a (manual) compound ID of {userID}{postID} and {userID}{followedID} respectively. It allows you to perform simple actions of unliking and unfollowing without querying for the document first. Ex) firestore().collection('postsaves').doc(`${userID}${postID}`).delete()

Final Thoughts

You mention maybe moving to AWS. I have worked much more in Firebase than in AWS, but I have done both. In my opinion, Firebase is unmatched in both usability and documentation. There are some compromises in terms of functionality and fine tuning but I recommend sticking with Firebase if the lack of text searching is the only hurdle.

Firebase best data structure practice for large dataset

When we are talking about speed, the most important rule in Firebase is to have the data as flatten as possible. According to this rule, i suggest you remodel your database like this:

firebase-url
|
--- users
| |
| ---- userId_1
| | |
| | ---- userName: "John"
| | |
| | ---- userAge: 30
| | |
| | ---- products
| | |
| | ---- productId_1 : true
| | |
| | ---- productId_2 : true
| |
| ---- userId_2
| |
| ---- userName: "Anna"
| |
| ---- userAge: 25
| |
| ---- products
| |
| ---- productId_3 : true
| |
| ---- productId_4 : true
|
---- products
|
---- productId_1
|
---- productName: "product_1"
|
---- users
|
---- userId_1: true
|
---- userId_2: true

In this way you can query your database very simple to display all the users that have access to a single product: firebase-url/products/productId/users/ and also all the products of a specifc user: firebase-url/users/userId/products/

And this how it can be done in code:

DatabaseReference usersRef = FirebaseDatabase.getInstance().getReference().child("users").child(userId).child("products");
ValueEventListener eventListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
String productId = (String) ds.getKey();

DatabaseReference productsRef = FirebaseDatabase.getInstance().getReference().child("products").child(productId);
ValueEventListener valueEventListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
String productName = (String) dataSnapshot.child("productName").getValue();
}

@Override
public void onCancelled(DatabaseError databaseError) {}
};
productsRef.addListenerForSingleValueEvent(valueEventListener);
}
}

@Override
public void onCancelled(DatabaseError databaseError) {}
};
usersRef.addListenerForSingleValueEvent(eventListener);

To learn more about structuring your Firebase database correctly please read this post.

Hope it helps.

Firebase Realtime Database - Database trigger structure best practice

It looks like you've nested two types of data under a single branch, which is something the Firebase documentation explicitly recommends against in the sections on avoid nesting data and flatten data structure.

So instead of merely splitting the code into two, I'd also recommend splitting the data structure into two top-level nodes: one for each type of data. For example:

"notes-data": {
note-1234: {
author: "-L1234567890",
members: {
"-L1234567890": 0,
"-LAAA456BBBB": 1
}
}
},
"notes-access": {
note-1234: {
title: "Hello",
order: 1
}
}

By using the same key in both top-level nodes, you can easily look up the other type of data for a note. And because Firebase pipelines these requests over a single connection such "client side joining of data" is not nearly as slow as you may initially think.

Firestore structure for users interactions in social media app

Posting this as a Community Wiki, since this was commented by @DougStevenson and it answered the question:

If you can make queries to satisfy your app's requirements, then yes, it's good enough.
That's all that really matters. If it doesn't satisfy queries, then you will need to step back, define your queries carefully, then structure your data to satisfy them.

NoSQL data modeling typically follows the queries you need, so define those first.

Firebase: How flat should my data structure be?

If you only keep one position per user (as seems to be the case by the fact that you use singular user_position), there is no useful difference between the two structures. A user's position in that case is just another attribute, just one that happens to have two value (lat and lon).

But if you want to keep multiple positions per user, then your first structure is mixing entity types: users and user_positions. This is an anti-pattern when it comes to Firebase Database.

The two most common reasons are:

  • Say you want to show a list of user names (or any specific, single-value attribute). With the first structure you will also need to read the list of all positions of all users, just to get the list of names. With the second structure, you just read the user's attributes. If that is still much more data than you need, consider also keeping a list of /user_names for optimal read performance.
  • Many developers end up wanting different access rules for the user positions and the other user attributes. In the first structure that is only possible by pushing the read permission from the top /users down to lower in the tree. In the second structure, you can just give separate permissions to /users and /user_positions.

Firebase rules for user list best practice?

Hey I'm not a pro or affiliated with Firebase. But as I have encountered same type of problems I share my thoughts with you.

  1. I don't think allowing users to search others based on emails stored in a Firebase node and directly from client side is entirely safe. Searching based on somethign like userName is ok because it is scoped to your app.
  2. If you must, then I would either make it a 2 step process using Firebase functions (which you can put another layer of security check in there also no user is directly reading from database) or introduce other parameters that all need to pass for a successful query. Something like a temporary unique id that expires after awhile.
  3. If you still want to share emails, you can store user's sensitive information in a separate node and only save what you really need to expose to others in a public node which can still have some security rules protecting it form access of someone who is not logged in, for instance and you map the emails by UIDs.

Just some thoughts.

EDITS

  1. You can provide a way for users to be able to search others by username (similar to instagram for instance.) and in firebase you only have to connect each username with their UID. So people can find each other via username. Imagine this in firebase (you can do the same of emails so a person making request need to know an email to get UID not the other way) :

    user_names : {
    alice_d: UID,
    bob_ross: UID,
    ....
    }

You can later search for any user name without exposing others simply by using .equalTo() in your query or run more complex queries via FireStore (I am new to it too) or using a search system that has your data indexed already like Algolia.


  1. Facebook provides further information such as list of friends if you app is approved so you can always use that list to suggest friends granted that users have logged in by Facebook O'auth and your app has the priviledge to see friends lists.

  2. See here for how to verify a user making https requests in Firebase functions. In your function you can do the search and only return what is safe back to the client. (keep in mind the speed might be an issue unless your function is running frequently). And for making the request from client side, you do something like this.

    _makeRequest: function() {
    this.user.getIdToken().then(function(token) {
    //token is a long string JWT token used to identify the user to a Firebase service.
    var req = new XMLHttpRequest();

    req.onload = function() {
    /*you return the result in your function*/
    if (JSON.parse(req.responseText).rslt === "SUCCESS") {

    }
    }.bind(this);

    req.onerror = function() {

    }.bind(this);

    /*attaching location key*/
    req.open('GET', 'https://us-central1-rest-of-function-address-found-in-
    firebase-functions', true);
    req.setRequestHeader('Authorization', 'Bearer ' + token);
    req.send();

    }

You can also make this happen by writing something to database and have a function to run onCreate(), see here if you need more info on Firebase functions. Hope this helps.



Related Topics



Leave a reply



Submit