Firestore Data Model

https://firebase.google.com/docs/firestore/

Firestore is a NoSQL database which is offered through Firebase. It’s an upgraded version of the Firebase Realtime Database with everything the Firebase Realtime Database offers and much more.

Features:

  • Flexible and scalable NoSQL document-oriented database
  • Supports mobile, web, and server development
  • Keeps data in sync across apps in realtime
  • Offline support with offline reads and writes
  • Seamless integration with Firebase and GCP products
  • Cloud Function triggers off Firestore create/update/delete/write

Source

Firestore Data Model:

Firestore has the concept of collections and documents. You can think of a Collection as an array of objects. Where a document is a single object in that array (collection).

Collection:

A collection can be thought of as an array of objects. In this example, we have a collection of users and each document is a single user.

Example:

users = [
{
"name": "John Doe",
"email": "johndoe@example.com"
},
{
"name": "Leeroy Jenkins",
"email": "leeroyjenkins@example.com"
}
]

Document:

A document is a single object in the array of objects. Below, we can see the properties of a single document from the collection of users.

Example:

{
"name": "John Doe",
"email": "johndoe@example.com"
}

Root Collection vs Sub Collection:

A root collection is a collection at the top level of your Firestore database. A sub collection is a collection on a document. For instance, below we have multiple collections at the root and a sub collection on the nodejs-vs-python document.

Example — Overview of Collection Types:

/posts                # <-- Root Collection
/art-in-america # <-- Document
/nodejs-vs-python
...
/comments # <-- Sub Collection
/12345ADVK # <-- Document
/456DADJJA
/users                 # <-- Root Collection
/boblee123 # <-- Document
/dakotaz
name = dakota # <-- Document name field
age = 24 # <-- Document age field
/treefall

When should I use root collections versus sub collections?

Firestore supports deeply nesting data through sub collections. However, when designing NoSQL databases all though it’s schemaless we still need to think about the data model. A good practice is usually to think about how your application needs to query data. Then build your structure around this.

Example One — Root + Sub Collection:

This is an example which demonstrates a likely use case with something like comments on a forum. If you will not need to search through all the comments then having comments as a sub collection makes a lot of sense.

/posts                             # <-- Root Collection
/art-in-america # <-- Document
title = Art in America
/comments # <-- Comments as sub collection
/12345ADVK
/456DADJJA

Example Two — Multiple Root Collections:

This is an example of using two root collections instead of one root collection and one sub collection. Giving you the ability to search through all the comments rather than having to do a complex query where you loop through each post document. This structure allows you to link the comments back to a single post by passing an id.

/posts                             # <-- Root Collection
/art-in-america # <-- Document
/nodejs-vs-python
/comments                          # <-- Comments as root collection
/12345ADVK
post_id = art-in-america # <-- post_id links to a post
/456DADJJA
post_id = art-in-america
/789FKLBLI
post_id = nodejs-vs-python

Example Three — Duplicated Data:

This an example of duplicating data for more performant queries. As you can see we now have comments as a sub collection on the root posts collection. We also have comments as a root collection which links back to a single post using the post_id.

/posts                          # <-- Root Collection
/art-in-america # <-- Document
/comments # <-- Comments as sub collection
/12345ADVK
/456DADJJA
/comments                       # <-- Comments as root collection
/12345ADVK
post_id = art-in-america # <-- post_id links to a post
/456DADJJA
post_id = art-in-america

Now when a user visits a single post we can show them the post comments without making two queries. However, if we need to search all the comments we can make a single query to the root comments collection. Rather than having to query each post document and then query that documents sub collection of comments across n..posts. Which would easily get out of control.

Note: You will need to now update the comments data in two places

Modeling Data Relationships:

You can model data relations such as one-to-one, one-to-many, and many-to-many. Below are a few examples of what this looks like in Firestore.

Example One — One-to-One:

This is an example of a one-to-one relationship with Firestore. As you can see underneath the art-in-america document we have a field called, author_id. Which allows us to model one author to one post.

/posts                         # <-- Posts as root collection
/art-in-america
author_id = johndoe # <-- author_id links to user
/users                         # <-- Users as root collection                      
/johndoe
name = John Doe # <-- Document name field
age = 22 # <-- Document age field

Example Two — One-to-Many:

This is an example of a one-to-many relationship with Firestore. Giving you the ability to search through all the comments rather than having to do a complex query where you loop through each post document. This structure allows you to link the comments back to a single post by passing an id.

/posts                          # <-- Root Collection
/art-in-america # <-- Document
/nodejs-vs-python
/comments                       # <-- Comments as root collection
/12345ADVK
post_id = art-in-america # <-- post_id links to a post
/456DADJJA
post_id = nodejs-vs-python
/789FKLBLI
post_id = nodejs-vs-python

Example Three — Many-to-Many:

This is an example of a many-to-many relationship with Firestore. Imagine a scenario where you want to only allow a user to like once on a post. With the structure below you can query the likes collection and find out quickly if a user has liked the post or not. The query would look a bit like this, /likes/${post_id}_${user_id} where post_id and user_id are dynamically passed into the query.

/posts                             # <-- Posts as root collection
/nodejs-vs-python # <-- Document
title = NodeJS vs Python
/comments # <-- Comments as sub collection
/12345ADVK
/456DADJJA
/likes                             # <-- Likes as root collection
/nodejs-vs-python_ryan
post_id = nodejs-vs-python # <-- post_id to posts
user_id = johndoe # <-- user_id to users
/nodejs-vs-python_tyler
post_id = nodejs-vs-python
user_id = tylerj123
/users                             # <-- Users as root collection                      
/johndoe
name = John Doe # <-- Document name field
age = 22 # <-- Document age field
/tylerj123
name = Tyler Jones
age = 27

Review

There are many different ways to model data in Firestore. Hopefully, the above gives you a good starting point to dive into more advanced concepts. If you want to make sure you’re thinking about your data model correctly. Ask yourself questions about how your application works. Try and narrow down the different ways in which you will get data out of your Firestore database. This will go along way. Good Luck!

What’s next?

Stay tuned for weekly releases of the Angular Firebase series. Where we will be covering hosting, databases, cloud functions, cloud storage, and authentication! To get updates when the next article is released, hit follow.

Image: serverlessguru.com

If you would like to learn more, check out our courses on Serverless, AWS, Firebase, and GCP at serverlessguru.com.

If you have any questions you can reach me at serverlessguru@gmail.com.

Good luck with your journey into the world of Serverless!

Thanks for reading :)