Firebase
Firestore

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.

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.

  1. 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 to add if not mentioned.

Note: When listenForChanges is set to true, the first response from the API will be {"message": "Subscribed to API", "documents": []}. Be aware of this if your response handling involves checking whether documents is empty.

  1. 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:

Add:

The add operation creates a new document in a collection with a specified or auto-generated ID.

  1. 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 how FieldValue 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

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.