Firebase Query for Products in Categories (database setup and best practice)

Viewed 1743

TL;DR products have multiple categories. View should show only all subcategories and products that have the category assigned.

How to setup the DB and queries?


I'm learning Vue and Firebase at the moment coming from a C# and SQL background, I need some help and advice on the noSQL side of things.

EDIT: categories and products are two separate collections (at the moment).

I have products, which can have multiple categories (see product 5)

products: {
  prod1-id: {
    name:'apple-type-A',
    price: 2
    cats: cat1-id
  }
  prod2-id: {
    name:'apple-type-B',
    price: 2.5
    cats: cat1-id
  }
  prod3-id: {
    name:'banana-type-A',
    price: 1.6
    cats: cat2-id
  }
  prod4-id: {
    name:'banana-type-B',
    price: 1.9
    cats: cat2-id
  }
  prod5-id: {
    name:'smoothie',
    price: 5,
    cats: [cat2-id, subCat1-id, subCat2-id]
  }
}

Those categories are a tree.

categories: {
  cat1-id: {
    name: 'fruits',
    subCat1-id: {
      name: 'apple'
    }
    subCat2-id: {
      name: 'banana'
    }
  },
  cat2-id: {
    name: 'MySmooth'
  }
}

The customer should see the first tree.

The landing page should only show the first depth of the category tree and every product without a category (maybe add a category called 'no category').

When you click on a category is should show all the subcategories and products, that have this category.

This goes on until the deepest branch.

I tried to sketch my idea:

Customer views


VIEW

For programming I use Vue, with vuex and vuexfire and as framework Vuetify.

I have the complete product management setup but I don't know how to query for this view.

My idea was to reuse a <v-card v-for="p of products"> I already have and is working fine.

But this shows only products, not categories. How do I get the categories into the mix?

QUERY

Working with vuexfire this is quite simple.

bindSoldProducts: firestoreAction(({ bindFirestoreRef }) => bindFirestoreRef('products', productsColl.where('isSold', '==', true))

but how can I get the categories and products now show side by side?

I've started to get the products together by querying, but have no idea how to put the categories into the mix:

firebase.firestore.collection('products').orderBy('name', 'asc').onSnapshot(snap => {
  const productsArray: any = []

  snap.forEach(doc => {
    const product = doc.data()
    product.id = doc.id
    productsArray.push(product)
  })

  store.commit('setAllProducts', productsArray)
})

Do I need to structure my database differently?

Do I just query the products and use "some js logic/magic" to show the categories? But how would I then get the views.

Are the collection group queries of firebase the right way to go? If yes, how?

Please advice

2 Answers

Try a for in all Categories and get the fruits:

const categories = {
 "cat1-id": []
}

Object.keys(categories).forEach(cat => {
   const res = db.collection('products').where('cats', 'array-contains', cat).get()
   // Resolve and put (push) in categories[cat]
})

See: https://firebase.google.com/docs/firestore/query-data/queries

I'm writing my response an answer as it was too long for a comment.

If I understood your use case correctly, you want to have a DB structure where you have a list of products and their respective categories which both will be displayed simultaneously side by side on your Vue application view.

While I'm not a Vue expert, I can suggest using the following structure for your Cloud Firestore database: Products/{product}/categories/{category}, which translates to collection/{document}/collection/{document}.

In the Cloud Firestore data model you can have a collection within another collection, and this is called a subcollection. For example, you could have Products/fruits/Categories/banana and inside the banana document you could have type, price, or id fields among many others.

Then, you could use Collection group queries to retrieve documents from a collection group instead of from a single collection. A collection group consists of all collections with the same ID. By default, queries retrieve results from a single collection in your database.

For example, you could create a categories collection group by adding a Categories subcollection to each product and then retrieve results from every product's categories subcollection at once, let's say bananas:

const querySnapshot = await db.collectionGroup('categories').where('productName', '==', 'banana').get();
querySnapshot.forEach((doc) => {
  console.log(doc.id, ' => ', doc.data());
  // Banana from Mexico, Banana from Honduras, etc.
});

Before using a collection group query, do not forget to create an index that supports your collection group query. You can create an index through an error message, the console, or the Firebase CLI.

Related