0

I'm working on an app with Firestore. I use Firebase Authentication for managing users. The thing is that most of the data in my app would be user-specific. Meaning that users should only have access to their own data.

I've already done research about Firestore security rules, so I know how to handle the security part.

However, I'm wondering how to properly model user-specific data in general. First piece of data I'm adding are categories, that have (for now) 2 properties: name and type.

After some research, I found it's good to create a collection called categories and then a document per-user, each named after the user's ID (UID).

But then, within such a user-specific document, I want to add my categories. One way I figured it out is like on the screenshot below:

firestore data model - document per user in categories collection, then maps with name and type for each category

So, to describe this approach in a more generic way:

  1. New collection for each data type (e.g. categories)
  2. Inside the collection, separate document named with UID for each user
  3. In each user-specific document, a map with an object's data (e.g. category_1 map with fields name = groceries and type = expense

However, what worries me here is that I need to somehow invent these names of the maps like category_1, category_2 etc... I have a feeling something is wrong in this model, but my strong SQL background doesn't allow me to think that through

Do you have any ideas whether this model is a good one or what problems could it produce later? Maybe you can suggest a better approach for modeling user-specific data in Firestore database?

Dawid Sibiński
  • 1,657
  • 3
  • 21
  • 40

1 Answers1

4

Is there any limit on how many categories can a single user have? If not then it'll be better to create a collection for categories to avoid hitting 1 MB max document size. If there is a limit and you decide to use a map, I'd recommend creating a map field categories and them as it's children as shown below so if you add any other fields in the document, it'll be much more categorized:

{
  categories: {
    category_1: {
      name: "",
      type: ""
    },
    category_2: {
      name: "",
      type: ""
    }
  }
}

However, creating a sub-collection could be better choice as well if each category gets more data in future and you need some queries on categories.

users -> {userId} -> categories -> {categoryId}
(col)     (doc)        (col)          (doc)

As the categories are in a sub-collection you don't need to add userId field in every category document. However you can also create 2 different root collections namely - users and categories:

users -> {userId}
(col)      (doc)

categories -> {categoryId}
(col)           (doc)

In this case you would have to store userId field to filter between owner of those categories.

@Alex Mamo has perfectly explained difference between using a root level collection and a sub-collection in this post if you need an in-depth explanation: What are the benefits of using a root collection in Firestore vs. a subcollection?


Security rules will be different in both the cases. If you use sub-collections then you can just read user UID from the wildcard like this:

match /users/{userId}/categories/{categoryId} {
  allow read, write: if request.auth != null && request.auth.uid == userId;
}

However, if you use a root level collection, the document ID for each category usually won't have user's UID but the UID will be stored in the document as a field. In that case you would have to read the userId from the document being requested.

match /categories/{categoryId} {
  allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
}

what worries me here is that I need to somehow invent these names of the maps

NoSQL database don't have a schema. You can dynamically add any key as required. Just treat every document as a JSON object.

const newKey = "category_123"
const newValue = {name: "newCatName", type: "expense"}

const userDoc = firebase.firestore().collection("users").doc("userId")
userDoc.update({
  [`categories.${newKey}`]: newValue
})

This update operation will create a new child in 'categories' map with the provided values.

Dharmaraj
  • 47,845
  • 8
  • 52
  • 84
  • 1
    Thanks for that answer. It cleared my mind about what was worrying me :) I think I'll go with `users` top collection and `categories` subcollection, i.e. users -> {userId} -> categories -> {categoryId}. Categories are not global/shared between users in my app, they will always be processed within a user context. I think this would make querying and security easier. – Dawid Sibiński Aug 20 '21 at 02:21