Query Data Scalably for Actions on Google using Cloud Firestore

Mandy Chan
Google Developers
Published in
8 min readJul 12, 2018

Do you need to share data between your Actions for the Google Assistant and your mobile and web apps? Are you looking to avoid hardcoding the content of your Action so you can make changes without redeploying your code? In this blog post, we will show you how to use Cloud Firestore to address these types of use cases together with the Actions on Google Node.js client library. If you’re a developer who is already familiar with Firestore, you can continue reading to learn about integrating Actions on Google with your project! The goal of this post is to show you how easy it is to use Cloud Firestore and create an Action that can store and query data from the cloud.

As an example, we’ll be building an Action called Planet Glossary, which tells you the description of a planet. You can use this exercise to build your own glossary for a domain you’re passionate about. Once it is deployed and reviewed, your users can have a conversation with your Action from any of these surfaces: Google Home devices, Smart displays, Android phones, Android Auto and Chromebooks. Here’s the dialog that you’ll be building after reading this blog post:

Before getting started

In order to successfully follow along in this post, you must understand the following:

  • How to create and deploy a project using Firebase command line tools

Firebase 101 + Actions On Google

  • How to create an intent and add training phrases — Systers Workshop 1
  • How to use conv.ask() and Fallback Intent — Systers Workshop, Exercise #1

Developer Entity

As you can see from above, this is a simple dialog which uses conv.ask() as well as the Fallback Intent.

In the Planet Glossary, we have a predefined list of planets that the user can ask about. The planets here are referred to in Dialogflow as developer entities. Developer entities allow us to define what planet names are in the new entity type, planets.

To create a new developer entity, go to the Dialogflow console and, in the left menu, select the + sign next to Entities:

Next, click CREATE ENTITY. This brings you to a page where you give the entity a name; in my case, I’m calling it planet (yes, I know the moon isn’t really a planet, but there’s a reason for it! We’ll explain it in the next post). The screenshot below is the list of values for our planet entity. It is always good if you can think of multiple synonyms for each term. In this case, we can say Mercury is the “first planet from the sun” as a synonym. Once you’ve finished the list, remember to click SAVE!

Tip: Make sure that you always click SAVE when you make changes, since this is not automatic. Not only does it save your newly defined entity, but it also triggers the training of the agent so it better understands what the user is saying.

Once you’ve successfully created a developer entity, it appears as @<developer entity>. In this case, it appears as @planet, as shown below. This means your entity has been created and the agent is trained. If you want to learn more about entities, I recommend watching Dialogflow Entities: Identify things your users mention by my colleague, Dan! ;)

Now, let’s create the ask_planet_intent after you’ve created the planet entity. We need this intent because we want to capture the planet entity from the user and respond with the corresponding definition of the planet to the user. The entire action has 4 intents, as shown below:

Create ask_planet_intent

The following screenshot shows what the ask_planet_intent looks like. In our training phrases, you’ll notice that the planets are automatically highlighted for us. This occurs whenever you create a training phrase and use a known entity value or one of its synonyms.

Remember to enable the fulfillment on the bottom of the page. This is the only intent that uses webhooks, and we will go over the code later in this post.

How to look up definitions in Cloud Firestore

In this Action, we’re going to store the definitions in a database called Cloud Firestore, a scalable NoSQL Cloud database. There are a couple of reasons we’re going to use Firestore instead of hardcoding all of the definitions. Let’s examine them briefly.

The most important reason is the ability to add, delete, or change words in our glossary without having to redeploy the code. Additionally, we want to allow for new requirements like keeping track of how many times a term is looked up. We’ll cover this in the next blog post.

In this post, we’re going to build a read-only version of Planet Glossary. This means that we will not be changing data in Firestore; instead, we’ll just access it.

Here’s how the data looks in Firestore:

Enable the Firestore in the Firebase console

Go to the Firebase console, under the Develop section, choose Database and select Create database as shown in the image below:

You then need to select the security rules for your Cloud Firestore. In this case, we just accept the default Start in locked mode. Click Enable after you’ve selected the mode.

After the security rules are set up, you will see the following screen which shows you an empty database:

Now, go ahead and click + Add collection. You’ll be prompted to give your collection a name, followed by another prompt for creating your first document. This is because a collection can only exist when there is at least one document in it.

Below is an example of the first document that we will create. Type in what you see in the image below and then click Save.

After you click Save, you should see a document that looks like the image below:

Now go ahead and create a document for each of the remaining planets.

After the database is set up on the Firebase console, we will work on getting Firestore integrated with our Actions.

Integrate Firestore with Actions on Google

To run and deploy our backend code, we use Cloud Functions for Firebase because of its ease of scaling and zero maintenance. You can read more in Silvano’s post on Deploy your Action fulfillment webhook using Cloud Functions for Firebase.

To get access to the resources in our Firebase project, we need to first add the firebase-admin dependency to our script.

Run npm install firebase-admin — save inside the functions folder, which also contains the package.json file. This adds the firebase-admin library to our project, which we will use to connect and interface with the database.

Now, let’s go over the code shown below, which we would add to the index.js file inside of our functions folder. You can access the code here.

Once the firebase-admin has been added as a dependency to the project, we’ll need to require firebase-admin in the code, as shown on line 5:

const admin = require(‘firebase-admin’);

admin.initializeApp();

By calling this method, we are creating and initializing a Firebase app instance. In a nutshell, admin.initializeApp gives your script the information it needs to properly access the Cloud Firestore database.

At this point, we have initialized our firebase app and provided it with information about the database location and the credentials needed to authenticate. Next, we need to create the Firestore client, which represents a Firestore Database and is the entry point for all Firestore operations. We can create the client by calling the method admin.firestore():

const db = admin.firestore();

Understand the ask_planet_intent

Let’s break down the “ask_planet_intent” handler and understand how we will access a definition for the user.

To retrieve the contents of a single document, you first create a reference to the “planets” collection and get the document reference, which contains the definition of the word along with some other data:

const collectionRef = db.collection(‘planets);

const termRef = collectionRef.doc(term);

When we use the term reference, it’s important to understand what it means. “A reference is a lightweight object that just points to a location in your database.” We’re going to use this reference to retrieve the definition of the word from Firestore.

Now that we have a variable called termRef, which is an instance of DocumentReference, we can retrieve the data by calling the get() method.

Tip: Because this callback function happens asynchronously and the termRef is returning the promise, we MUST return this promise from our function. See line 17. If you don’t do this, then you’ll see the following error message in the Firebase Log:

If you want more information on using promises in Cloud Functions, you can watch Javascript Promises with a Firestore Triggers in Cloud Functions by the awesome @CodingDoug.

At this point, I think you’re probably asking yourself, “What is this variable called snapshot?”. This variable is a DocumentSnapshot that contains the data for the document. When you call .data(), you can access all the fields we defined in the Firestore database earlier. In our example, we only need two fields, definition and word from the database, because we are building an Action that reads from Firestore and tells the user the definition of the word “moon”.

In the case where the promise is fulfilled, we use conv.ask on line 20 because we want to continue the conversation by asking the user, “what else do you want to know?”. On the other hand, if an error occurred, we use conv.close on line 25 to end the conversation and exit the Action.

You can find all the code for the index.js file here on Gist. Now, you’re ready to create an Action that can look up data in Firestore. If you already have an application that uses Firestore, you can then expose the data in an Action. If you’re interested in learning some more advanced conversational features, please join @MandyChanNYC on July 31st for the second Systers workshop — you can watch the recording here. It is open to everyone! I look forward to having you join us!

--

--

Mandy Chan
Google Developers

| ☆ Creator of SSML Builder | Google Assistant Developer Advocate | I write about Actions on Google | Voiceholic on http://bit.ly/gcp-mandy