I'm making a blog app using firebase.
I want to know the best practice of data structure.
As far as I know, there are 2 case.
(I'm using react native)
case 1:
posts
-postID
-title,content,author(userID),createdDate,favoriteCount
favorites
-userID
-favoriteList
-postID(onlyID)
-postID(onlyID)
In this case, for example, when we need to get favorite posts.
firebase.firestore().collection(`favorites/${userID}/favoriteList`)
.get()
.then((snapshot) => {
snapshot.forEach((favorite) => {
firebase.firestore().collection(`favorites/`).doc(`${favorite.id}`)
.get()
.then((post) => {
myPostList.push(post.data())
});
});
in this case, we can't order the favorite posts by createdDate. So, need to sort client side. Even if so, we don't use limit() function.
case 2:
posts
-postID
-title,content,author(userID),createdDate,favoriteCount
favorites
-userID
-favoriteList
-postID
-title,content,author(userID),createdDate,favoriteCount
-postID
-title,content,author(userID),createdDate,favoriteCount
firebase.firestore().collection(`favorites/${userID}/favoriteList`).orderBy('createdDate','desc').limit(30)
.get()
.then((snapshot) => {
snapshot.forEach((post) => {
myPostList.push(post.data())
});
});
in this case, When the favorite post is modified by the author,
we have to update all of the favorite posts. (e.g. If 100 users save the post as a favorite, we have to update to 100 data.)
(And I'm not sure we can increment favoritecount by a transaction, exactly same.)
I think if we use firebase.batch(), we can manage it. But I think it seems Inefficient.
It seems that both ways are not perfect. Do you know the best practice of this case?
What about using arrays or Collection Groups?
solution 1: arrays
posts
-postID
-title,content,author(userID),createdDate,favoriteCount
-[favoriters(userID)]
Now you can query for a user's favorites by querying posts that "array-contains" the user's ID. You can also modify individual posts without iterating through a bunch data copies.
There's a limit to this approach though. Maximum size for a document is 1 MiB; assuming that a user ID is 4 bytes, a document can contain no more than 250K favoriters. Clients would also have to do some O(N) processing to add / remove favoriters.
solution 2: Collection Groups
posts
-postID
-title,content,author(userID),createdDate,favoriteCount
-favoriters {collection}
-userID
A collection group consists of all collections with the same ID. By default, queries retrieve results from a single collection in your database. Use a collection group query to retrieve documents from a collection group instead of from a single collection.
So we can fetch a user's favorite posts via
db.collectionGroup("favoriters").whereEqualTo("userID", <userID>).get();
To favorite a post, we just do
const postsRef = db.collection("posts");
postsRef.document(<postID>).collection("favoriters").add({ "userID", <userID> });
Maybe not a direct answer to your question, but the official documentation has an example for that:
Working with arrays, lists, and sets
Summary: Store and query data in array-like structures in documents.
Use case: If your app requires complex data objects like arrays,
lists, or sets, follow the model outlined in this solution. For
example, in a blogging app, you might want to create a set of related
posts.
https://firebase.google.com/docs/firestore/solutions/arrays
Related
I'm building a fullstack todo app using firebase to store user information. My idea is that each user can create multiple lists and write multiple 'todos' in every list (eg. Have a list for today and another for tomorrow). My data is structured in this manner:
users (collection)
-> usersID (every user is a document)
-> todos Lists (collection) -> todoId (document) -> (object with the data)
I can't seem to be able to query the multiple lists a user might have (by default, I'm currently just retriving data from the 'todos' collection). Either I'm failing to see some firestore functionality or I'm not structuring my database correctly. Any advice?
You are structuring your data fine. The problem is that the Firebase Client SDK doesn't offer a function to retrieve all subcollections a document has. To solve your problem you got multiple options.
1. The cleanest but most exhaustive way
You upgrade to the Blaze Plan and use Firebase Functions with the Admin SDK. The Admin SDK offers a function called listCollections() which exactly does what you want. The disadvantage is that Firebase Functions have something called coldstart which means that when your function was idle for some minutes it takes up to 20 seconds (from my experience) to run again after you called it, which can be really frustrating. Otherwise you can run a free node.js server on something like Heroku to use the Admin SDK there to avoid coldstart (better option in my opinion). The Admin SDK is explicitly not meant to be used on the client side. Check this thread
2. List all your todo lists (easiest way)
Everytime the user creates a new list, you add that to an array in your users document like this:
// Representation of your user document
user
name: "Florian"
lists: [
"todos",
"anotherList"
]
So in that way you can just fetch your user, get the lists property from it and call all lists with a loop over the lists array. As alternative just display the lists content and let the user open it explicitly to save reads.
3. Restructuring your database (I would recommend it)
If you don't have a lot of data yet, I would recommend to restructure your database like:
users - the collection with all your users (without the todo list subcollections
lists - containing a document for every created list
- each document contains a field with `owner`
- each document contains a field with the list name
- each document owns a subcollection with todos
If you use this way you could just query the database like
const getLists = async () => {
const ref = collection(db, 'lists');
const q = query(ref, where('owner', '==', theUserYouWantToQuery);
// returns array with all list (including listname) of that user.
const lists = (await getDocs(q)).docs.map(doc => ({ ...doc.data(), doc.id }));
}
when you have all list objects from that user you can either loop over that array to get all todos in the lists or let the user only fetch one list by opening it
I am trying to access the child value of a unique key value (that had been "pushed") within Firebase. Currently, my database looks like this: I want to access the value of "emailOfUser"
I am very new to Firebase so I am not familiar with the functions. Currently, this is my method of obtaining other values for a different section of the database:
Thank you so much for any feedback!
I've tried different methods to accessing this data within the Firebase, but I cannot get it to work/the methods I were using were outdated. I also tried to "update" the Firebase instead of "pushing" the values to prevent a unique key from generating, but it simply overwrote my current data rather than appending something new.
If you want to load all the users who voted and print their emails, you can do that with:
get(child(dbref, 'usersWhoVoted')).then((snapshot) => {
snapshot.forEach((childSnapshot) => {
console.log(childSnapshot.key, childSnapshot.val().emailOfUser);
});
})
Note that your current structure allows a user to vote multiple times. If you want to only allow them to vote once, use some identifier of the user as the key in your database structure:
userVotes: {
"uniqueIdOfUser1": "valueTheyVotedOn",
"uniqueIdOfUser1": "valueTheyVotedOn",
...
}
Now each user can by definition only vote once, If they vote again (assuming your security rules allow that), their new vote will simply replace the existing vote.
Recently I came across a problem, I am making a function where you can get rid of chats in my app so I have this function
function deleteconvo() {
const me = firebase?.auth?.currentUser?.uid;
const members = [me, recipient.uid];
firebase.db.collection("conversations").remove({ members });
}
Firebase is telling me that:
firebase.db.collection("conversations").remove({ members }); is not a function
I also tried .delete and I cant find any answers online. Anyone have a solution to this?
note: I understand that I have to grab the docs value too, because I am in the conversation collection of the the database each conversation has its own unique ID, which I dont know how to grab and store that in a variable.
A good starting point is the official Firestore documentation regarding how to delete data.
In that page you can find examples in different languages on how to delete documents, fields and collections.
For example to delete an entire collection or subcollection in Cloud Firestore you need to retrieve all the documents within the collection or subcollection and delete them.
And to delete a document you can use the delete() method:
const res = await db.collection('cities').doc('DC').delete();
`
I have data model like this
Players-->root collection
Sports--->root collection
Players_Sports---root collection
I want get all the sports(Multiple sport) details or document player belongs.
For this I am using angularFireStore5
First, I am getting
Player details like this
this.db.collection('players').doc(id).get().toPromise()
Second, I am getting Player(user) linked PlayerSport
db.collection<any>('PlayerSports',ref => ref.where('playerId', '==', id) ).get().toPromise()
Third, I am trying to get Sports details based on ID'S,
db.collection<any>('sportsType', ref => ref.where('sportsType' 'in' [sportsIDs])).get().toPromise()
where SportIDs is arrary of ID that are linked in player_sports
First and Second steps works fine, but third steps is not giving any data or response
Could you please let me know where is the problem,
is it in Data model or code? my guess is that data model is not correct. Please guide me on this.
I would suggest getting the data from firebase and storing it inside a list so the app can access it later.
void getDataF(){
databaseReference
.collection("TableName")
.getDocuments()
.then((QuerySnapshot snapshot) {
snapshot.documents.forEach((f) {
iDFacList.add(f.documentID);
dbFacList.add(f.data["FieldName"]);
});
});
}
There is no sportsType field in the sportsType document as far as I can see.
If you're trying to find documents based on their SportsId field, you'll want ref.where('SportsId'....
Update
It seems that you're trying to find a document by its ID, which you can do with:
ref.doc(sportsIDs)
If you want to get multiple documents, or get a single document as a collection, you can use:
ref.where(firebase.firestore.FieldPath.documentId() 'in' [sportsIDs])
If I have a subscription
Meteor.publish('itemsByProjectId', function (projectId) {
check(projectId, String);
return Items.find({ projectId: projectId});
});
then I only subscribe items related to the project.
Do I also have to use Items.find({ projectId: projectId }) when outputting the items in the templates or is it sufficient to use Items.find() since the collection is already filtered through the subscription.
What is best practice in such situations? I don't see the need for filtering the data again.
it would depend on if you had sent any other docs from the Items collection to the client. If you had used subs manager to cache a past sub to the items collection or were using another subscription to the Items collection, then you would want to be more specific in your client side find within a template helper. However, if you are certain that the only docs of the item collection that will be on the client at this point are the ones with the correct projectId, then it would be ok to just use the find without a selector.