How To Structure / Model Firebase Cloud Firestore ← Vue.js
Building a collection of list items organized by sections is one of the scenarios that you can’t avoid when building any type of application. In this guide, I will be showing you exactly how to do that by building a simple restaurant menu page using Vue.js and Firebase Cloud Firestore.
Along the way, you’re also going to learn how to properly structure and query Cloud Firestore Database for it.
Let’s take a look at what we will be building by the end of this tutorial.
In the typical example above, I have a restaurant name at the top. Below that you can see menu items grouped by sections/types such as Appetizers, Dum Briyani, etc.
Pretty straight forward.
- Final JavaScript Object API Structure
- Firestore Data Structure for Restaurant Menu Page
- Create A Vue.js Project
- Initialize Firebase
- Query Restaurant Data
- Retrieve & Merge Sub-Collections Data [Menu Types / Menu Items]
- Render Restaurant API Object To The View
Final JavaScript Object API Structure
Before going further, I would like to talk about the structure of the final JavaScript object that will be needed to build this restaurant menu page.
The picture below depicts a high-level overview of how to transform the final JavaScript object into the actual restaurant menu page by showing which data goes where.
To generate this object, simply get the data from Cloud Firestore and merge them together which is very similar to API output when fetching data from HTTP request).
Let’s take a look at the output JavaScript object in detail.
It has title
and id
properties at the top level as well as a menu
property which is an array that contains a couple of objects which will be the restaurant sections/types.
Each section/type object in the menu array has a title
property and menuItems
which is another array of objects. Each object inside the menu items consists of title, description and price properties.
Data Structure For The Menu Page
I could create a data structure very similar to the output JavaScript object, but it will have deeply nested data inside a single document which must be avoided at any cost.
Instead, I will be making a sub-collection wherever new data will be added over the period of time. Then, query & merge them together in order to match the final JavaScript object.
Cloud Firestore queries are shallow, so when you make a query to a document, it will not have a sub-collection on it.
Recommended → Learn Firestore CRUD Queries Quickly [Guide]
Here is the infographic that shows how to structure data for a restaurant menu page.

As you can see, I have a restaurant collection, which has a few documents. Each document has two fields: the title
which holds string value and menu-types
which is a sub-collection.
Documents on the menu-types
sub-collection again have two fields; the title
that has string value and menu-items
which is another sub-collection.
Each document in that sub-collection has three fields which are title
, description
, and price
.
Here is the screenshot of an actual Cloud Firestore database structure with sample data.

Add some data similar to the above structure to your Cloud Firestore Database so that it’s easy to follow along.
Create A Vue.js Project
1. Go ahead and create a vue.js project using webpack.
vue init webpack my-project
2. Then, npm install to it.
npm install
3. Run the application.
npm run dev
4. Once the vue.js app is running, go to HelloWord.vue
and delete the file.
5. App.vue
is the file I will be putting all my code into to make it simpler for this tutorial.
6. Add Materialize CSS link to index.html
which is optional.
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
Initialize Firebase
1. Install Firebase.
npm install --save firebase
2. Initialize Firebase inside main.js
.
import firebase from "firebase";
var firebaseConfig = {
apiKey: "*************************",
authDomain: "*************************",
databaseURL: "*************************",
projectId: "*************************",
storageBucket: "*************************",
messagingSenderId: "*************************",
appId: "*************************"
};
firebase.initializeApp(firebaseConfig);
Get initialization code from your Firebase project on the Firebase console.
Query Restaurant Data
- Create a couple of properties
restaurant
anderror
insidedata()
method.
data() {
return {
restaurant: {},
error:""
};
},
2. DefinegetRestaurantData()
function inside methods:{}
object.
Add async
keyword in front of the getRestaurantData()
function as I will be making a few asynchronous operations inside it. As you can see, this function also takes a single parameter called resId
.
Inside the try-catch block, declare function scoped object called restaurant
which will have the final output JavaScript object.
methods: {
async getRestaurantData(resId) {
try {
var restaurant = {};
// Get Restaurant Data
var resRef = firebase.firestore().collection("restaurants");
var resSnap = await resRef.doc(resId).get();
restaurant = resSnap.data();
restaurant.id = resSnap.id;
console.log(restaurant)
return restaurant
} catch (e) {
return {
errorMsg: "Something went wrong. Please Try Again."
}
}
}
}
Get a reference to restaurant collection and store it in resRef
. Then call doc()
method on it by passing resId
as an argument.
Run a get() method on it as well, which is an asynchronous call that returns a promise which will become fulfilled when a document snapshot is available on to the variable resSnap
.
To pause the async operation, use await
keyword in front of it which will make sure to wait until the restaurant snapshot (resSnap) is ready before executing further code and convert it to JavaScript object by running data()
method on the resSnap
.
Finally, assign the data to the restaurant
object including document id and return it.
3. Call getRestaurantData()
method inside created() {}
function.
Inside created()
method, invoke getRestaurantData()
method by passing an actual restaurant ID as an argument which will return a promise as well.
To get a hold of the value, call the then
method on that promise and pass an anonymous function to it which will have the returned data as a response.
created() {
this.getRestaurantData("anjappar-chettinad").then(response => {
if (response && response.errorMsg) {
this.error = response.errorMsg;
} else {
this.restaurant = response;
}
});
},
Inside the callback function, check to see if the response has a specific property called errorMsg. If yes, assign errorMsg
to the this.error
property otherwise assign it to this.restaurant
.
4. You may get an error message saying “Missing / Insufficient Permission” when querying Cloud Firestore on the browser console.

To fix that, go to Firebase Database → Rules Tab and paste the following:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
}
}
}
In a real-world scenario, you are going to have a security rule for each collection either root level or sub-collections.
Recommended → 6 Must Know Firestore Security Rules
5. When you run the app at this point, you are going to see restaurant
object with title
and id
properties on the console.

But why? What happened to the menu-types sub-collection? This happened because as I mentioned earlier Cloud Firestore queries are shallow, so it only retrieves documents with fields but no sub-collections.
To get sub-collections, you will have to make a separate query to get the menu-types (sections).
Recommended -> Learn Firestore CRUD Queries Quickly [Guide]
Retrieve & Merge Sub-Collections Data
1. Inside getRestaurantData()
function, obtain a reference to menu-types
sub-collection.
2. Run a get()
method on it which will return a promise. This promise will become fulfilled when a menu-types snapshot is available.
Again, use await
in front of the query in order to make it synchronous, which will stop executing the next statement until the snapshot becomes available.
// Get Restaurant Menu Types
var menuTypesSnap = await resRef
.doc(resId)
.collection("menu-types")
.get();
restaurant.menu = [];
for (const menuTypeObj of menuTypesSnap.docs) {
var menuType = menuTypeObj.data();
menuType["menuItems"] = [];
// Get Restaurant Menu Items
var menuItemsSanp = await menuTypeObj.ref
.collection("menu-items")
.get();
for (const menuItem of menuItemsSanp.docs) {
menuType["menuItems"].push(menuItem.data());
}
restaurant.menu.push(menuType);
}
Then, create a property called menu
inside the restaurant
object and set it’s value to an empty array for now. This is where I am going to be adding menu sections/type objects.
Now, iterate through the menuTypeSnap
using for-of and convert documents to JavaScript objects using data()
on them and store them into the menuType
object.
You could use forEach instead of for-of. But for-of will wait to finish the synchronous operation before going to the next interation which is exactly what I wanted.
Now, Let’s get the items from the menu-items
collection and store them inside the menuItems
property which is another array inside menuType
object.
Let’s get a reference to the menu-items
collection. Using the await
keyword, it will pause the loop until the snapshot becomes available.
Once getting the menuItemsSnap
, loop through them, convert each snapshot to JavaScript object and push it to the menuItems
array which is the property of menuType
object.
Finally, push menuType object to restaurant.menu
property on each iteration.
At this stage, the scoped restaurant
object will have the correct data structure that is needed to build the view.
Here is the full source code of getRestaurantData()
code.
methods: {
async getRestaurantData(resId) {
try {
var restaurant = {};
// Get Restaurant Data
var resRef = firebase.firestore().collection("restaurants");
var resSnap = await resRef.doc(resId).get();
restaurant = resSnap.data();
restaurant.id = resSnap.id;
// Get Restaurant Menu Types
var menuTypesSnap = await resRef
.doc(resId)
.collection("menu-types")
.get();
restaurant.menu = [];
for (const menuTypeObj of menuTypesSnap.docs) {
var menuType = menuTypeObj.data();
menuType["menuItems"] = [];
// Get Restaurant Menu Items
var menuItemsSanp = await menuTypeObj.ref
.collection("menu-items")
.get();
for (const menuItem of menuItemsSanp.docs) {
menuType["menuItems"].push(menuItem.data());
}
restaurant.menu.push(menuType);
}
return restaurant;
} catch (e) {
return {
errorMsg: "Something went wrong. Please Try Again."
};
}
}
}
Recommended → Build A Secure To-Do App with Vue + Firestore [Authentication]
Render Restaurant API Object To The View
Inside the container class, which is part of Materialize CSS, add a div element that is responsible to show an error if any.
Create li inside ul and show the restaurant title which is one of the top-level properties on the restaurant object.
Make a ul element for each section by looping through the restaurant.menu which is an array of objects (menu sections/types) and show the title of menu section/type using the menu.title property which will print Appetizers, Dum Briyani, etc.
<template>
<div id="app">
<div class="container">
<div class="red" v-if="error">{{error}}</div>
<ul class="collection with-header">
<li class="collection-header #212121 grey darken-4 white-text">
<h4>{{restaurant.title}}</h4>
</li>
</ul>
<ul class="collection with-header" v-for="menu in restaurant.menu" :key="menu.id">
<li class="collection-item pink white-text">
<div>{{menu.title}}</div>
</li>
<li class="collection-item" v-for="item in menu.menuItems" :key="item.id">
<div>
{{item.title}}
<br />
<span class="grey-text">{{item.description}}</span>
<a href="#!" class="secondary-content">${{item.price}}</a>
</div>
</li>
</ul>
</div>
</div>
</template>
You will need to make a nested loop in order to show menu items on each section properly.
To do that, iterate through menu.menuItems
array and pull title
, description
and price
properties and show them to the appropriate place like in the code above.
If everything goes well, you should have a restaurant menu page similar to the screenshot below.

Conclusion
In this quick tutorial, I showed you how to create a simple restaurant menu page that has menu items organized by section. Along the way, you learned how the data structure stored on the Cloud Firestore differs from the final output JavaScript object structure.
Finally, you understood how to query and merge data from Cloud Firestore using for-of loop and await-async to match the final JavaScript object which is then converted into the restaurant menu page.
Question for you❓
How would you structure your data when building an application similar to this?
If you have a different approach, feel free to share it in the comment section below…
I am looking forward to hearing from you…Thank you for reading…
Recommended →Build A Secure To-Do App with Vue + Firestore [Authentication]