Ezfire Logo

Comparison

Cloud Firestore vs. Realtime Database: Which Firebase database is right for you?

In this article we will compare and contrast the two databases offered by Firebase, Realtime Database and Cloud Firestore, and analyze their similarities and differences

Author Profile Picture
Dec 23, 2021 · 8 min read

Ezfire


Firebase is easily the most popular backend-as-a-service platform available today. According to Google there are over 3 millions apps on the Firebase platform, and with easy to integrate features like user auth, realtime data and file storage it is easy to to see why.

At the core of the Firebase platform are the two databases that enable all the realtime functionality, the Firebase Realtime Database and Google Cloud Firestore. Both these databases are powerful NoSQL databases that allow developers to effortlessly built reactive apps by listening to realtime data streams directly in the frontend.

The Firebase Realtime Database is the original Firebase database that is still supported and promoted by Firebase at Google. Cloud Firestore is the newcomer, being built from the ground up to address a number of real issues developers had when scaling out apps built with the realtime database, while maintaining to useful Realtime capabilities.

In this article we are going to compare and contrast these two data stores, analyze their similarities and differences, and zero in on the use cases where they excel.

As in the previous article, all Firestore and Realtime Database queries included in this article can be run directly on Ezfire by signing up for our app.

But with that out of the way, let's take a look at how data is modelled for both Realtime Database and Cloud Firestore.

Data Model

There are some strong similarities the data models for Cloud Firestore and Realtime Database are very much alike. When using both Cloud Firestore and Realtime Database, you store JSON data at resource paths like users/:userId or users/:userId/addresses/:addressId. There is a big difference however in how this mechanism is implemented.

In Cloud Firestore, data is organized into collections of JSON documents each with there own resource path. When you read a document at a given path, you load only that documents data. Data at child paths must be loaded manually each time it is needed. For example, when loading data from the resource path users/:userId, the returned document does not contain any data from users/:userId/addresses/:addressId. These two documents are completely isolated.

In Realtime Database, the entire database acts as one large JSON tree. As such, when you read and write data to the database, it behaves as if all the data is connected. As such, when loading data from a resource path in the Realtime Database, the returned document includes all child resources for that path. For example, if you were to load the document at resource path users/:userId, it would also include all the child data. Following the example above, that means the users/:userId document would also include users/:userId/addresses/:addressId. This behaviour can sometimes be convenient but requires you to think carefully about how you will store your data to avoid cases where you are always loading too much.

It's worth noting as well that Cloud Firestore has native support for a number of non-standard JSON data types, such as timestamps, geopoints and document references.

Reading Data

For both Cloud Firestore and the Realtime Database, communicating with the database is done using one of the official SDKs, such as the NodeJS SDK used to run queries on Ezfire. Let's take a closer look at how to read data from the Realtime database.

With Realtime Database, the most important method isref. This method is how you build paths that you want to read from. For example, to read the user with id userId you could do something like:

db.ref(`users/${userId}`).get();

This will return the JSON subtree located at users/:userId relatively to your JSON root. Note that as described above, this will also load any subdocuments of users/:userId such as users/:userId/addresses/:addressId if they exist and are nested under the user document.

In Cloud Firestore, the above query would look something like this:

// Using chained builders
db.collection("users").doc(userId).get();

// Using document path
db.doc(`users/${userId}`).get();

This query will return only the document located at users/:userId and none of the sub documents. They would need to be loaded independently.

Now, what if we have a collection of users and we want to query the collection to find a specific user or a subcollection of users. The options for doing this with the Realtime Database are quite limited. Using a combination of orderByChild and startAt, endAt and equalTo allows you to select a subcollection of documents matching certain range conditions. For example if you want to find all the users that were 25 years old, you could do this:

db.ref("users").orderByChild("age").equalTo(25).get();

Similarly to find all the users in there 30s you can do:

db.ref("users").orderByChild("age").startAt(30).endBefore(40).get();

This however is really all you can do.

Cloud Firestore on the other hand is much more flexible when it comes to querying collections. You can, for example, easily replicate the Realtime database queries above:

// All users who are 25 years old.
db.collection("users").where("age", "==", 25).get();

// All users in their 30s.
db.collection("users").orderBy("age", "asc").startAt(30).endBefore(40).get();

You can also filter by multiple fields, using an expanded set of operators:

// Users who are not 25.
db.collection("users").where("age", "!=", 25).get();

// All users using google or facebook login.
db.collection("users")
  .where("authProvider", "in", ["google", "facebook"])
  .get();

// The first 10 users older that 25 that are signing in with Google.
db.collection("users")
  .where("age", ">=", 25)
  .where("authProvider", "==", "google")
  .limit(10);

All of these queries are possible with Cloud Firestore but not possible with Realtime Database. The only caveat to the above Firestore queries is that complex queries across multiple fields often require a composite index to back them as Cloud Firestore requires that all queries have indexes.

Writing Data

Writing data to the Realtime Database is very simple. You only need to specify the ref of your data in the JSON tree, and call set:

db.ref(`users/john`).set({
  name: "John",
  age: 25,
});

To update this data without completely overwriting what is already there, we just use update:

db.ref(`users/john`).update({
  age: 26,
});

To delete data, you just need to write null to a given ref:

db.ref(`users/john`).set(null);

Finally, if you are working with an array in your data model, you can use push to add a new element to the array with a unique key:

db.ref(`users/john/posts`)
  .push({
    message: "My first post!",
  })
  .get();

Cloud Firestore offers similar functionality to Realtime Database, but represents it differently:

// Write the document `users/john` to the database.
db.collection("users").doc("john").set({
  name: "John",
  age: 25,
});

// Update the document.
db.collection("users").doc("john").update({
  age: 26,
});

// Delete the document.
db.collection("users").doc("john").delete();

That is pretty much all there is to writing data with Cloud Firestore. Since Firestore does not behave like a singl JSON tree like Realtime Database, it offers two simple ways of atomically acting on subarrays of documents. Instead of using push, you simple write to the database using a sentinel field value:

db.collection("users")
  .doc("john")
  .update({
    postIds: FieldValue.arrayUnion("asdfghjkl"),
  });

Cost

Realtime Database is priced based on the amount of data returned from the database each month in Gigabytes. The cost is $5/GB of data stored in the database and $1/GB of data read from the database. This cost structure makes Realtime Database good for applications that require a lot of reads of small documents and can get quite expensive when fetching large documents from the database. This becomes especially relevant when we recall that Realtime Database will load the entire subtree of a ref when fetched from the database. This can easily lead to high costs if the data model requires a lot of nested data.

Cloud Firestore has an easy to understand pay-per-use cost structure. The costs are $0.06 for 100000 reads, $0.18 for 100000 writes, and $0.02 dollars for 100000 deletes. This makes for an easy to understand cost for an application but also has the potential to become very expensive if app traffic grows rapidly. This cost structure is good for applications that do a smaller number of reads and writes of large documents, as you pay the same price per read no matter the size of the document.

Conclusion

In this article we took a look at the Firebase Realtime Database and Cloud Firestore and compared their features and use cases. We found that there are a lot of similarities between the data model and data access patterns of these two databases, but that Cloud Firestore has a much more flexible system for querying documents from a collection. We also found that due to the pricing, Realtime Database is good for applications that require a large number of reads of a small amount of data, whereas Cloud Firestore excels in applications that require a fewer number of reads of a large amount of data. Thanks for reading, I hope you learned something useful.

If you are interested in getting more experience with Cloud Firestore or Realtime Database, sign up for our app and try out some of the queries in this article on our sample database.


Ready to get started?

Ezfire will always be free to use for individuals. Try out Ezfire risk-free.