Calling Cloud Firestore APIs
Cloud Firestore (opens in a new tab) is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud. Ensemble platform provides a deep integration with the Firestore DB and allows you to call operations on your data in Firestore effortlessly.
Unlike traditional relational databases, Firestore offers a document-based structure well-suited for storing and managing various data types within oour app. Firestore is a great choice for Ensemble applications because it provides simplified data modeling, Offline Persistence, Real-time Updates, and API Convenience to interact with data.
Now, let's dive into performing basic operations on our Firestore database:
Operations on Firestore won't work unless we have configured our Ensemble application with Firebase. Learn how to configure it here.
- To get hands-on experience with firestore operations, check the live example on Ensemble Studio (opens in a new tab)
Firestore Data types
Timestamp
Following methods are available for Firestore's Timestamp type.
Timestamp.now()
Static method. Returns a Timestamp representing the current time.
Example
var currentTimestamp = Timestamp.now();
console.log(currentTimestamp);
Timestamp.fromDate(date)
Static method. Creates a Timestamp from a JavaScript Date (opens in a new tab) object. Example
var specificDate = new Date('2024-08-05T12:34:56Z');
var specificTimestamp = Timestamp.fromDate(specificDate);
console.log(specificTimestamp);
Timestamp.fromMillis(milliseconds)
Static method. Creates a Timestamp from a given number of milliseconds since the Unix epoch (January 1, 1970).
Example
var milliseconds = 1691237696123; // Milliseconds since the Unix epoch
var timestampFromMillis = Timestamp.fromMillis(milliseconds);
console.log(timestampFromMillis);
new Timestamp(seconds, nanoseconds)
Constructor. Creates a Timestamp object from a given number of seconds since the Unix epoch and additional nanoseconds.
Example
var seconds = 1691237696;
var nanoseconds = 123456789;
var customTimestamp = new Timestamp(seconds, nanoseconds);
console.log(customTimestamp);
toDate()
Converts a Timestamp to the number of milliseconds since the Unix epoch. Example
var milliseconds = customTimestamp.toMillis();
console.log(milliseconds);
valueOf()
Returns the number of milliseconds since the Unix epoch, similar to toMillis(). It’s used when Timestamp is compared to other values in arithmetic operations.
Example
var value = customTimestamp.valueOf();
console.log(value);
Following properties are also available on each Timestamp
object
seconds
The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
Example
var timestamp = new Timestamp(10,0);
console.log(timestamp.seconds); //prints 10
nanoseconds
The number of nanoseconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
Example
var timestamp = new Timestamp(10,0);
console.log(timestamp.nanoseconds); //prints 0
FieldValue
Other than isEqual
, Ensemble platform supports all the methods for FieldValue (opens in a new tab).
All the methods are static and are called exactly the same way as you could call in js, see this (opens in a new tab) for example.
See the following paragraphs for examples of how to use FieldValue.serverTimestamp()
1. Creating a Firestore Collection
To store data in Firestore, we need to create a collection. A collection in Firestore is a container for documents, which are individual pieces of data. Each document contains a set of key-value pairs. Follow the given steps to create a collection:
- Go to the Firebase Console.
- Select our project.
- Navigate to Firestore Database in the side menu.
- Click on Start collection.
- Enter a collection ID (e.g., sports).
- We can add our first document by entering a document ID or let Firestore auto-generate one for us.
- Add fields and values to our document.
- Click Save.
By default, firestore rules do not allow anyone to access our database. To get started with it, update the rules by changing it to allow read, write;
which allow everyone to access our database and then update the rules according to our requirements.
2. Types of Firestore Operations
Firestore offers various operations to interact with our data. Here's a breakdown of some core operations along with demo API calls for our Ensemble app
Get:
This operation retrieves data from our Firestore collections. We can either retrieve entire collections or use queries to filter and sort our data.
- Example (Get all users):
getUsers:
type: firestore
path: users
listenForChanges: true
Explanation:
type: firestore
: Specifies that the operation is for Firestore and it is not a RestAPI.path
: The path to the collection or document from where we wanna retrieve the data.listenForChanges
: The operation will listen for real-time updates if set to true and triggers the UI to update.
Default vale for
operation
is set toadd
if not mentioned.
Note: When
listenForChanges
is set totrue
, the first response from the API will be{"message": "Subscribed to API", "documents": []}
. Be aware of this if your response handling involves checking whetherdocuments
is empty.
- Example (Get user using multiple filters):
getSpecificUsers:
inputs:
- userId
type: firestore
path: example/users
query:
where:
- field: _documentId
operator: ==
value: ${userId}
orderBy:
- age
limit: 10
Explanation:
inputs
: We can also use dynamic variables in the path or a query.path
: Path can be any based on our collections and documents we are trying to access.query
: Filters the data based on the conditions such as where (opens in a new tab). orderBy, Limit (opens in a new tab).
Add:
The add
operation creates a new document in a collection with a specified or auto-generated ID.
- Example:
createProject:
inputs:
- userId
- proName
- proDes
- proFiles
type: firestore
path: users/${userId}/Projects
operation: add
data:
proName: ${proName}
description: ${proDes}
createdAt: ${FieldValue.serverTimestamp()}
Explanation:
operation: add
: Indicates that a new document will be created.data
: The fields and values for the new document. Note howFieldValue
is being used to tell Firestore to set the server timestamp
Set:
The set
operation can create a new document if it does not exist but if the document already exists, set
will overwrite
the entire document with the data provided, unless we use the merge
option.
- Example:
setProject:
inputs:
- userId
- projectID
- proName
- proDes
- proFiles
type: firestore
path: users/${userId}/Projects/${projectID}
operation: set
data:
proName: ${proName}
description: ${proDes}
setAt: ${FieldValue.serverTimestamp()}
Update:
The update
operation only updates the fields specified in the provided data. If the document does not exist, update
will fail with an error.
- Example:
inputs:
- userId
- projectID
type: firestore
path: users/${userId}/Projects/${projectID}
operation: update
data:
# Below files will be stored as Array of objects.
files: [{ name: "index.js" , lines: 78 },{ name: "LMS.js" , lines: 245 }]
lastUpdated: ${FieldValue.serverTimestamp()}
Delete:
The delete
operation removes a document from a collection.
- Example:
deleteProject:
inputs:
- userId
- projectID
type: firestore
path: users/${userId}/Projects/${projectID}
operation: delete
Collection Group:
The isCollectionGroup
feature is used to retrieve specific collections from any collection. For example, if we have 100 documents in the users
collection and each document has a sub-collection named projects
, the isCollectionGroup
feature helps in getting all projects
directly rather than iterating through each document.
- Example:
getAllProjects:
type: firestore
path: Projects
isCollectionGroup: true
3. Response of Firestore Operations
When performing Firestore operations, we may need to manipulate the responses to fit our application's needs. Below are some common ways demonstrating how to use YAML for API calls, handle states, and display data in our app.
1. Firstly, we will make an API call as follow:
invokeAPI:
name: getProjects
inputs:
userId: ${userID}
We can also use
onResponse
&onError
on firebase API calls and can perform operations on response.
2. Using response in Column
:
To display data based on the API call's state (loading, success, error), you can use the following structure:
Column:
children:
- Column:
styles:
visible: '${getProjects.isLoading ? true : false}'
children:
- Progress:
display: circular
- Column:
styles:
visible: '${getProjects.isSuccess ? true : false}'
item-template:
data: ${getProjects.body.documents}
name: project
template:
projectDisplay: # that is an custom widget.
inputs:
name: ${project.proName}
des: ${project.description}
- Column:
styles:
visible: '${getProjects.isError ? true : false}'
children:
- Text:
text: "An error has occurred"
- Explanation:
- The first child
Column
is visible only when the API call is loading(visible: '${getProjects.isLoading ? true : false}')
. It shows a circular progress indicator. - The second child
Column
is visible only when the API call is successful(visible: '${getProjects.isSuccess ? true : false}')
. It iterates over the documents in the response body using item-template. - The third child
Column
is visible only when there is an error(visible: '${getProjects.isError ? true : false}')
. It shows an error message.
- The first child
3. Using response in Dropdown
:
To display data in a dropdown, we can use the following YAML structure:
Dropdown:
id: selectProject
label: Select Project
itemTemplate:
data: ${getProjects.body.documents}
name: project
value: ${project._documentId}
template:
Text:
text: ${"Name:" + " " + project.proName}
By using these operations, we can efficiently manage our data in Firestore with an Ensemble project. Firestore's real-time capabilities and simple API calls make it a powerful tool for any application.