Firebase & Google Cloud: What’s different with Cloud Firestore?
Previously in this blog series, I talked about the relationship between Firebase and Google Cloud Platform (GCP). In the last post, you learned about some of the specific differences with Cloud Functions. In this post, I’ll do the same with Cloud Firestore (Firebase, GCP). It’s a really good time to talk about Cloud Firestore, since it’s now generally available (Firebase, GCP) with a production quality service level agreement. I’m sure many of you have tried out Cloud Firestore already and are eager to use it in your apps!
Cloud Firestore is a massively scalable, cloud-hosted, NoSQL, realtime database. In order to structure your data, you define collections (similar to tables in SQL) which contain documents (similar to rows). Each document contains fields that contain the actual data. You can reference an individual document using its unique path, or you can query a collection for documents whose fields contain the data you’re looking for.
Cloud Firestore is unlike some of the other Cloud products that have a Firebase SDK for mobile development (such as Cloud Functions and Cloud Storage). Instead, the Cloud Firestore product is a collaboration between the Cloud and Firebase teams, combining the expertise with realtime data, learned from Firebase Realtime Database, and the scalability of Google Cloud Platform.
Cloud Firestore has SDKs for mobile apps and server components (which I will discuss later), but the underlying database product is the same. If you’re a Google Cloud developer using Firestore as part of your backend architecture, or you’re a Firebase developer whose users access it directly from your client app, Cloud Firestore stores data and scales in exactly the same way. However, there are some differences you should be aware of.
In the console
Using the Cloud console, you can browse data in the Cloud Firestore database in your project. It looks like this, where the left column lists all the top-level collections. When you select a collection, the middle column lists the collection’s documents by their unique IDs, and finally the right column shows the fields in a selected document.
This is similar to the view of the same data in the Firebase console:
Both consoles have a way to switch between showing data and showing indexes. In the Cloud console, you switch between the two in the navigation panel on the far left, whereas in the Firebase console, you switch between tabs above the content area.
Unique to the Firebase console, you’ll notice additional tabs for Rules and Usage. Usage shows a graph of reads, writes, and deletes for the current quota period:
This is convenient for Firebase developers, who might try to get a sense of what the per-user usage is like for the app that they’re working on.
If you look carefully, you can also see where your usage stands against the free quota per day. Also notice the link at the very bottom, which sends you off to the Cloud console for more detailed billing metrics:
Data, indexes, and billing are all essentially the same in the consoles for both Firebase and Cloud. But the tab labeled “Rules” is special to Firebase. I’ll get to that in a bit. First, let’s talk about the client libraries that Firebase adds to Cloud Firestore.
SDKs for web and mobile apps
GCP provides SDKs for working with Cloud Firestore in server-side code. There is support for Python, Node.js, Java, C# (.NET), Go, PHP, and Ruby. These SDKs are expected to run in a trusted environment, where you have control over execution. For web and mobile apps, where you must ship the code to the end user to run directly, Firebase provides additional libraries for iOS, Android, web (and likely, in the future, for games running on Unity and C++).
One important optimization that the client SDKs give you is the ability to cache documents on the mobile device. As long as offline persistence is enabled, all documents previously read are automatically cached on the device. The next time the client queries for a document, if the cached version is up to date with the version on the server, the cached version will be used, saving you money and your users time. It also works while the device is offline. This caching feature is available only with the client SDKs provided by Firebase, but not with the server SDKs.
Another difference between the mobile and server SDKs for Cloud Firestore is the assumption about where the code is executing. Server code typically runs in a low latency environment, especially within other Google Cloud products. In this situation, other optimizations are in place to help database operations (especially transactions, which must round-trip between the caller and the database) complete more quickly.
Firebase also provides the Firebase Admin SDK (for Java, Python, Node.js, and Go), which is often used to implement some of the backend of your mobile app. Like the Cloud SDKs, the Admin SDK is meant for use exclusively on the backend and never for use in code that ships to end users. When working with Cloud Firestore using the Admin SDK, you should know that it effectively repackages, or wraps, the underlying Cloud SDK. The Admin SDK doesn’t offer any new APIs for Firestore — it simply gives you access to the existing Cloud API. As such, if you’re using the Firebase Admin SDK, you will end up consulting Cloud API documentation to understand how things work.
Backend code is typically considered to be “privileged” and normally operates without restrictions, because it’s assumed that you (the developer) have full control over the execution environment. When you initialize one of the Cloud Firestore server SDKs, you must provide a service account that gives permission to the SDK to perform actions in your project. So, if you need code that credits virtual money to your game’s players, your backend code can (and should) do that with no problems because your backend is authorized by a service account with access to your project. However, it’s highly unlikely that you want your end users to be able to modify the document that contains the record of their credits, intentionally or not! When you ship client code that accesses your database, you should assume that the code can’t be trusted, nor can the data it produces. Code running on end user devices can be modified and compromised to do whatever the attacker wants (after all, they may control the end user’s device entirely!). Since you can’t trust the end user and their device, you should implement appropriate security rules to protect your database. And it’s not even just malicious users — it’s also important to protect yourself from your own buggy code (we’re all guilty of this one)!
Cloud Firestore security rules are used to limit read and write access to Cloud Firestore coming from your mobile clients that are using one of the Firebase SDKs. This is unique to Firebase, and there is no equivalent on the Google Cloud side. The rules are enforced on the server, so they’re impossible for a client to bypass them.
By default, all access to Cloud Firestore coming from one of the mobile SDKs, or the Cloud Firestore REST API (when it’s presented with a Firebase Authentication user ID token) is rejected. To allow access to document, security rules typically employ some combination of:
- The user’s identity, as indicated by Firebase Authentication (also integrated into the client app)
- The new contents of the document they’re trying to write
- The existing contents of one or more documents.
So, you could easily write a rule that only allows access to a document if it has the same ID as the user’s authenticated user ID. And you could check to see if the fields being written have valid values. Note though, that security rules don’t implement a full programming language. You’ll notice that there are no loops, assignments, or procedural statements, but you have the ability to write complex expressions that evaluate to a final true or false value.
Bear in mind, however, when you access Cloud Firestore using one of the server SDKs, it always bypasses the rules. This goes back to the expectation that server code always runs in a trusted environment. If you fully control the code and the environment, then it’s assumed that you aren’t going to do anything harmful (again, excluding any bugs on your end!).
Lastly, if you don’t have end users working directly with Cloud Firestore in a mobile or web app, then you don’t need to be concerned about security rules. Firestore is perfectly usable on the backend without a client app. But if you are shipping an app, I’d strongly recommend integrating Firebase Authentication first, and you should think carefully about structuring your data and rules from there. There can be times when your data structure must follow security restrictions, such as when you want to store both public and private data for a user.
How are you using Cloud Firestore?
I’m curious to know how you’re making use of Cloud Firestore! Feel free to tweet the team @Firebase and ask questions to us on firebase-talk. And if you aren’t using Firestore yet, perhaps you could try one of the codelabs for Android, iOS, or the web. There’s no charge to get started with the free daily quotas.