The Stream Blog

Building an Ecommerce Chatbot with React Native and Dialogflow

Chatbots allows ecommerce sites to provide more to their customers without the need for human intervention. Conversational experience is a powerful way to sell more on your ecommerce site..

With the popularity of Chatbots, different services which provides powerful bot builders to developers also became available. A few of the most popular ones include Dialogflow, Azure Bot Service, and Wit.ai.

In this tutorial, I’m going to show you how to get started implementing your own chatbot by creating an ecommerce chatbot with React Native and Dialogflow.

Prerequisites to Build a Chatbot

Basic knowledge of React Native and Node.js is required to follow this tutorial.

This tutorial assumes that you have a Google account. You need it to sign up to Dialogflow. You also need an ngrok account to expose the server to the internet.

The following package versions will be used:

  • Node 11.2.0
  • Yarn 1.13.0
  • React Native CLI 2.0.1
  • React Native 0.59.9

We all know that React Native moves at a very fast pace so things can break rather easily. Try ensuring you’ve installed those specific versions in the event the app isn’t working the way I’ve described in the App Overview section.

Ecommerce Chatbot App Overview

The app that we will be creating is a React Native chat app in which the user talks with a bot which provides them with information about a specific product. In this case, it will be a yoyo store. So the user will inquire about a specific yoyo, and the bot will provide the details being asked.

The app is composed of two components:

  • React Native App - the interface where the user and the bot will interact.
  • Chat Server - provides the product information being asked by the user.

Here’s what the app will look like:

You can find the complete code of this app on this GitHub Repo.

Dialogflow Concepts

Before we proceed to the practical stuff for our ecommerce chatbot, it’s important to understand some of the basic Dialogflow concepts. But before that, let’s first get to know what Dialogflow is.

Dialogflow is a chatbot builder which is powered by natural language processing capabilities. They provide a way for your users to interact with your product via voice and text-based conversational interfaces. In a developer perspective, Dialogflow provides the tools for building smart chatbots that can easily integrate with your existing software to perform different operations.

Now that we know what Dialogflow is, it’s now time to learn about some of the basic concepts that you need to know in order to start building a chatbot.

  • Agent – the actual chatbot that which will respond to the user’s requests. In Dialogflow, you would normally create one agent per application. In this case, the agent is the ecommerce bot which responds to the inquiries of users regarding a specific product.

  • Intents – the action that you want the chatbot to perform depending on what’s being asked by the user. A single agent can have many intents. In the case of our app, we’ll only have three:

  1. list the available products
  2. select a specific product
  3. get a single info about the selected product

The intent is where you define the conversation flow for a specific action. The most common way is to add some training phrases. These are the phrases that you expect the user will utter when they want to perform the specific action. In the case of listing the products, the training phrases could be:

  1. list all products
  2. list all yoyos
  3. what do you have?
  4. which yoyos do you have?

Note that you don’t have to list out every possible phrase that you can think of. The agent will actually use those phrases for training so that it can derive all the other possible utterances that the user might use. Just remember that it won’t be perfect, so the more training phrases you can add, the better it will be at deriving the meaning from what the user has issued.

Entities - these are the objects or actors in a specific agent. Let’s use the following phrase as an example: “List all products”. In this case, “products” can be mapped to a specific entity by the same name. If the agent encounters that specific word or phrase, it’s going to give it meaning based on the examples that you’ve added on your entity. In this case, the meaning can be a few samples of the name of your products. In simple terms, entities are the ones which gives meaning to specific things that the user might say. You can then use that meaning to come up with a response. For the rest of the tutorial, I’ll be referring to any entity as @name_of_entity.

Context - represents the current state of the user’s request. If the user’s request is not yet fulfilled, the context is basically the data that’s gathered in the conversation flow up until now. In our case, the conversation might look something like this:

  1. Agent: what would you like to do?
  2. User: list all products
  3. Agent: lists all the products..
  4. User: select product B
  5. Agent: what would you like to know about product B?
  6. User: can I see the photo?
  7. Agent: shows the image..
  8. User: what year was it released?
  9. Agent: 2015

In the example above, the agent is able to keep the same context starting at step 4 when the user selected a specific product. From here on out, the agent assumes that the user has already selected a product. Until such time that the context expires, the agent will continue to use it. By default, it’s set to five turns so the user has to respond 5 more times before the agent forgets about it. Note that the user doesn’t have to wait five turns. They can also issue a different intent, and the agent will pick up on that.

  • Fulfillment - the agent doesn’t always have the answers to the user’s questions. Sometimes, it has to reach out to another server (via an API) in order to answer a specific question or perform an action. This is where fulfillment comes in, it lets you supply answers to the agent based on the data that you have on your own database.

Setting up Dialogflow

Now that you’re familiar with the basic concepts, it’s now time to set up Dialogflow. This section assumes that you already have a Google account.

Setting up the Agent

Start by going to the Dialogflow website and login with your Google account. Once you’re logged in, you’ll see the list of agents that you currently have. If you’re new to Dialogflow, this list will be empty.

From there, click on the Create Agent button to start creating an agent:

Once the agent is created, it will ask you to create an intent. Click on the Create Intent button to start creating an intent:

The first intent that we will create is the one which responds to the user’s request to list all the available products. Since we’re working on a yoyo shop, we’ll name the intent available_yoyos. At this point, you can also start adding the training phrases. These are the phrases that you expect your users might utter if they want to get the list of available products:

Once you’ve added all the training phrases, click on Save. This will start training the agent based on the phrases that you’ve added. Once you get a notification that the training has been completed, only then can you make sure that the agent will recognize the intent.

Note that we don’t need to add entities for this specific intent because there’s really nothing to derive out of the phrase that the user has entered.

Since the list of products will need to come from the server, we need to enable fulfillment for this specific intent. Later, we’ll add the URL to the server which will fulfill the request. Note that we haven’t added any text response because the server is the one that’s going to return it:

Next, we add the entity which corresponds to a specific product that we’re selling. In this case, we simply add the names of the yoyos. We also add the synonyms so that anytime those are used by the user, the agent will immediately know that it’s referring to the same thing:

Once you’ve saved the @yoyos entity. We can now proceed to creating the intent for handling the user’s request to select a specific yoyo:

From the screenshot above, you can see that we’ve added an output context. This will set the output context to whatever the user has specified in their intent. For example, if they typed “view loop 720”. In this case, “loop 720” is the name of the yoyo. That’s why we’ve highlighted it as a parameter which corresponds to the @yoyos entity. In Dialogflow, you can set the entity that a specific word or phrase will resolve to by highlighting it and selecting the entity.

As you can see in the screenshot below, this gets resolved to the value of yoyo. Since we’re setting it as an output context, this becomes the context for the next intent (getting a specific product data):

Under the Action and parameters section, the entity and their corresponding value is automatically mapped. All you have to do is set it as required. This is important because if the user simply types in “Pick” or “Select”, then the agent won’t be able to determine that they’re trying to pick a specific yoyo. Setting it as required will make the agent prompt the user for the specific parameter if it encounters keywords similar to what you’ve added on your training phrases:

Don’t forget to enable fulfillment for this intent.

Lastly, we need to add the intent for getting the details of the yoyo. But before that, we first need to add the @specs entity. This is where we define the details common to each yoyo. The values on the left side are the property names in the yoyos.json file, while the ones on the right are the other terms that the users might use to refer to that detail:

At this point, we can now add the learn_more intent. Add the training phrases and highlight the specific words or phrases that might refer to the @specs entity that we just added:

Getting the Dialogflow Config

The next step is for you to get your Dialogflow config file. This is the config file that you’ll need to add to the React Native app so that it can communicate with the agent that you created.

Click on the gear icon next to the agent name on the side bar. On the page that follows, click on the link to the Service Account:

That should redirect you to the Google Cloud Platform console. Click on the three vertical dots next to the middle row in the table and click Create key. That should prompt you to create a private key. Select JSON as the key type and click on CREATE to download the key file:

You’ll need to copy the contents of this file later to the config.js file on the app.

Bootstrapping the App

To get you set up quickly, I’ve prepared a starter branch on the GitHub repo which contains the dependencies of the app as well as a few minimal code. You can set it up by executing the following commands:

$ git clone https://github.com/anchetaWern/RNDialogflowEcommerceBot.git
$ cd RNDialogflowEcommerceBot
$ git checkout starter
$ yarn
$ react-native eject
$ react-native link react-native-dialogflow
$ react-native link react-native-voice

Once set up, update the config.js file with your Dialogflow config.

After that, you should be ready to run the app:

react-native run-android

OR

react-native run-ios

Building the App

Now we’re ready to build the app. We’ll first build the React Native app, then we’ll proceed with the server.

React Native App

Start by opening the App.js file and importing all the modules that we need:

import React, { Component } from 'react';
import { View, Text } from 'react-native';
import { GiftedChat } from 'react-native-gifted-chat';
import { Dialogflow_V2 } from 'react-native-dialogflow';

import { dialogflowConfig } from './config';

Aside from the View and Text components built into React Native, we’re also importing GiftedChat and Dialogflow_V2. GiftedChat allows us to easily build the chat UI while Dialogflow_V2 allows us to communicate with the version 2 of the Dialogflow API. A Dialogflow` module is also available, but that uses the version 1 of the API.

dialogflowConfig is the Dialogflow config file. Make sure that you’ve already set it up.

Next, we initialize the chatbot as a user and add their default message. This lets the user know that they’re speaking to a chatbot instead of a human:

const BOT_USER = {
  _id: 2,
  name: 'Yoyo Bot',
  avatar: 'https://ui-avatars.com/api/?background=0D8ABC&color=fff&name=YB'
};

class App extends Component {
  state = {
    messages: [
      {
        _id: 1,
        text: `Hi! I am the Yoyo Bot 🤖 from planet loop.\n\nHow may I help you with today?`,
        createdAt: new Date(),
        user: BOT_USER
      }
    ]
  };

  // next: add componentDidMount
}

const styles = StyleSheet.create({
  container: {
    flex: 1
  }
});

export default App;

Inside componentDidMount(), we initialize Dialogflow using the settings from dialogflowConfig:

componentDidMount() {
  Dialogflow_V2.setConfiguration(
    dialogflowConfig.client_email,
    dialogflowConfig.private_key,
    Dialogflow_V2.LANG_ENGLISH_US,
    dialogflowConfig.project_id
  );
}

Next, we render the UI using GiftedChat. All we need to supply here are the messages, the function for sending a message, and the info of the current user. In this case, we’re simply hardcoding the user info. Only the _id is required by GiftedChat. The value that you supply here should be the same as the user ID in the messages that you will send later on, so GiftedChat will recognize that it’s the current user:

render() {
  return (
    <View style={styles.container}>
      <GiftedChat
        messages={this.state.messages}
        onSend={messages => this.onSend(messages)}
        user={{
          _id: 1
        }}
      />
    </View>
  );
}

When the user sends a message, that’s the time where we want the bot to respond. Inside the onSend() method, we’re using the Dialogflow_V2.requestQuery() to make a request to the agent. In this case, we’re supplying the following:

  1. message - the message sent by the user.
  2. success callback function - executed when the request is fulfilled. Note that the request being fulfilled doesn’t always mean that the chatbot will respond correctly to the user’s query. Sometimes, it will just respond with the “Come again?” message if it doesn’t know how to process the user’s query.
  3. error callback function - executed when the agent encounters an error processing the request.
onSend(messages = []) {
  this.setState(previousState => ({
    messages: GiftedChat.append(previousState.messages, messages)
  }));

  let message = messages[0].text;
  Dialogflow_V2.requestQuery(
    message,
    result => this.handleResponse(result),
    error => console.log(error)
  );
}

Here’s the handleResponse() method. Below, we’re just extracting the fulfillment message that was returned from Dialogflow. This is the actual message that the chatbot responded with. Aside from that, we’re also extracting the payload. This contains the additional data that we want to pass to the function for constructing the message to render. This payload will come from the server that we will build later:

handleResponse(result) {
  console.log(result);
  let text = result.queryResult.fulfillmentMessages[0].text.text[0];
  let payload = result.queryResult.webhookPayload;
  this.showResponse(text, payload);
}

Here’s a sample response that you can get from Dialogflow:

{  
   "responseId":"xxxxx5cf-510c-xxxx-ae21-5d93e71xxxxxxxx",
   "queryResult":{  
      "queryText":"What do you have",
      "parameters":{  
         "yoyos":[  

         ]
      },
      "allRequiredParamsPresent":true,
      "fulfillmentText":"The yoyos available are: Hornet, Loop 2020, Loop Up, LP, Initiator, Loop 720",
      "fulfillmentMessages":[  
         {  
            "text":{  
               "text":[  
                  "The yoyos available are: Hornet, Loop 2020, Loop Up, LP, Initiator, Loop 720"
               ]
            }
         }
      ],
      "intent":{  
         "name":"projects/yoyo-shop-xxxx/agent/intents/867f6640-c360-xxxx-958a-xxxxxxxxx",
         "displayName":"available_yoyos"
      },
      "intentDetectionConfidence":0.7783524,
      "diagnosticInfo":{  
         "webhook_latency_ms":848
      },
      "languageCode":"en"
   },
   "webhookStatus":{  
      "message":"Webhook execution successful"
   }
}

Next, add the code for constructing the message which contains the chatbot response. In most cases, we’re simply appending the response that we get from Dialogflow. But if the response comes with a payload, that’s the time when the message changes a bit. As mentioned earlier, the payload will come from the server that we will be creating later. The payload is mainly used for containing the is_url field. When true, this means that the user has requested for the product image. To render it using GiftedChat, all we need to do is supply the image URL to the image property of the message:

showResponse(text, payload) {
  let msg = {
    _id: this.state.messages.length + 1,
    text,
    createdAt: new Date(),
    user: BOT_USER
  };

  if (payload && payload.is_url) {
    msg.text = "image";
    msg.image = text;
  }

  this.setState(previousState => ({
    messages: GiftedChat.append(previousState.messages, [msg])
  }));
}

That’s it for the React Native side of things. In the next section, we’ll go over the server for fulfilling the requests from Dialogflow.

Fulfillment Server

The server is the one responsible for fulfilling the request for a specific intent. In our case (ecommerce app), this is an API which returns the various details for a specific product.

We’ll use Node and Express to create the server. It’s going to have one route for fulfilling the requests. To keep things simple, the product data is hardcoded in a JSON file.

First, open the server.js file and import the modules that we need:

const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");

const BASE_URL = 'YOUR NGROK HTTPS URL';

require("dotenv").config();
const app = express();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors());
app.use(express.static('public'));

const yoyos = require('./yoyos.json'); // product data

The yoyos.json file is already included in the starter project. As you can see, it only contains basic information about each yoyo:

[
    {
        "id": "hornet",
        "name": "Hornet",
        "full_name": "Duncan Hornet",
        "manufacturer": "Duncan",
        "release_year": 2012,
        "image": "hornet.jpg",
        "weight": "52.3 g",
        "diameter": "58.62 mm",
        "width": "29.0 mm",
        "trapeze_width": "11 mm",
        "shape": "normal",
        "material": "Plastic (Polycarbonate/ABS/PS)",
        "bearing_type": "Ball Bearing",
        "bearing_size": "Size E (Raider)",
        "response": "Starburst",
        "axle": "N/A",
        "twist_apart": "Twist-Apart",
        "spacer": "Cover Type",
        "bind": "Tug Return",
        "maintenance": "Oil/Lube bearing"
    },
    {
        "id": "loop2020",
        "name": "Loop 2020",
        "full_name": "YoYo Factory Loop 2020",
        "manufacturer": "YoYo Factory",
        "release_year": 2018,
        "image": "loop2020.jpg",
        "weight": "52.1 g",
        "diameter": "58.63 mm",
        "width": "33.60 mm",
        "trapeze_width": "10 mm",
        "shape": "normal",
        "material": "Plastic (Polycarbonate/ABS/PS)",
        "bearing_type": "Ball Bearing",
        "bearing_size": "Size K (Loop720)",
        "response": "Starburst",
        "axle": "N/A",
        "twist_apart": "Twist-Apart",
        "spacer": "Cover Type",
        "bind": "Tug Return",
        "maintenance": "Oil/Lube bearing"
    },

    {
        "id": "loopup",
        "name": "Loop Up",
        "full_name": "Japan Looping Solutions - Loop Up",
        "manufacturer": "Japan Looping Solutions",
        "release_year": 2017,
        "image": "loopup.jpg",
        "weight": "51.0 g",
        "diameter": "58.55 mm",
        "width": "33.69 mm",
        "trapeze_width": "7 mm",
        "shape": "normal",
        "material": "Plastic (Polycarbonate/ABS/PS)",
        "bearing_type": "Ball Bearing",
        "bearing_size": "Size E (Raider)",
        "response": "Starburst",
        "axle": "N/A",
        "twist_apart": "Twist-Apart",
        "spacer": "Cover Type",
        "bind": "Tug Return",
        "maintenance": "Pad/Sticker/O-Ring Change Required"
    },

    {
        "id": "lp",
        "name": "LP",
        "full_name": "sOMEThING - LP",
        "manufacturer": "sOMEThING",
        "release_year": 2016,
        "image": "lp.jpg",
        "weight": "52.9 g",
        "diameter": "59.12 mm",
        "width": "34.48 mm",
        "trapeze_width": "10 mm",
        "shape": "normal",
        "material": "Plastic (Polycarbonate/ABS/PS)",
        "bearing_type": "Ball Bearing",
        "bearing_size": "Size K (Loop720)",
        "response": "Starburst",
        "axle": "N/A",
        "twist_apart": "Twist-Apart",
        "spacer": "Cover Type",
        "bind": "Tug Return",
        "maintenance": "Oil/Lube bearing"
    },

    {
        "id": "initiator",
        "name": "Initiator",
        "full_name": "C3yoyodesign - Initiator",
        "manufacturer": "C3yoyodesign",
        "release_year": 2016,
        "image": "initiator.jpg",
        "weight": "52.2 g",
        "diameter": "58.39 mm",
        "width": "34.97 mm",
        "trapeze_width": "10 mm",
        "shape": "normal",
        "material": "Plastic (Polycarbonate/ABS/PS)",
        "bearing_type": "Ball Bearing",
        "bearing_size": "Size K (Loop720)",
        "response": "Starburst",
        "axle": "N/A",
        "twist_apart": "Twist-Apart",
        "spacer": "Cover Type",
        "bind": "Tug Return",
        "maintenance": "Oil/Lube bearing"
    },

    {
        "id": "loop720",
        "name": "Loop 720",
        "full_name": "YoYoFactory - Loop 720",
        "manufacturer": "YoYoFactory",
        "release_year": 2017,
        "image": "loop720jp.jpg",
        "weight": "51.5 g",
        "diameter": "58.67 mm",
        "width": "34.10 mm",
        "trapeze_width": "8 mm",
        "shape": "normal",
        "material": "Plastic (Polycarbonate/ABS/PS)",
        "bearing_type": "Ball Bearing",
        "bearing_size": "Size K (Loop720)",
        "response": "Starburst",
        "axle": "N/A",
        "twist_apart": "Twist-Apart",
        "spacer": "Cover Type",
        "bind": "Tug Return",
        "maintenance": "Oil/Lube bearing"
    }
]

Next, add the route for fulfilling the requests:

app.post("/yoyos", (req, res) => {
  const { parameters, outputContexts } = req.body.queryResult;
  if (parameters.yoyos && parameters.yoyos.length) {
    return res.json({
      fulfillmentText: `What would you like to know about ${parameters.yoyos}?`
    });
  } else if (parameters.specs) {
    const yoyo_id = outputContexts[0].parameters.yoyos.replace(/ /g, '');
    const spec = parameters.specs;

    let spec_value = yoyos.find(item => item.id == yoyo_id)[spec];
    let payload = { is_url: false };
    if (spec == 'image') {
      spec_value = `${BASE_URL}/images/${spec_value}`
      payload = {
        is_url: true
      }
    }

    return res.json({
      fulfillmentText: spec_value,
      payload
    });
  }

  const names = yoyos.map(({ name }) => name);

  return res.json({
    fulfillmentText: "The yoyos available are: " + names.join(', ')
  });
});

Breaking down the code above, first we extract the data that we need from Dialogflow. In this case, all the data that we need is inside the queryResult in the request body:

  1. parameters - mainly contains the entities resolved from the user’s input. For example, if the user types “Select Yoyo X”, the intent triggered will be the select_yoyo intent. And the “Yoyo x” in the user’s message will resolve to the ID of the yoyo that we’ve added in the @yoyos entity.
  2. outputContexts - contains the data that was resolved from the previous context. For example, if the user types “Select Yoyo X” and we’ve set the output context to the user’s input. The next time the user types something, the output context will be what the user entered when that intent was triggered. In this case, “Yoyo X”. This will then be available in the outputContexts when the user types in something like “What’s the bearing size?”
const { parameters, outputContexts } = req.body.queryResult;

Here’s a sample request body that the fulfillment server receives from Dialogflow:

{  
   "responseId":"xxxx-xxx-4087-8df8-9f515xxxxxx2b-6xxxxx",
   "queryResult":{  
      "queryText":"Pick loop 2020",
      "parameters":{  
         "yoyos":"loop2020"
      },
      "allRequiredParamsPresent":true,
      "outputContexts":[  
         {  
            "name":"projects/yoyo-shop-xxxx/agent/sessions/xxxxxxx-bdc6-e74e-xxxx-9849ea4xxxxx/contexts/yoyo",
            "lifespanCount":5,
            "parameters":{  
               "yoyos":"loop2020",
               "yoyos.original":"loop 2020"
            }
         }
      ],
      "intent":{  
         "name":"projects/yoyo-shop-kjbpri/agent/intents/xxxxxx-5fa3-4778-9778-xxxxxxxx",
         "displayName":"select_yoyo"
      },
      "intentDetectionConfidence":1,
      "languageCode":"en"
   },
   "originalDetectIntentRequest":{  
      "payload":{  

      }
   },
   "session":"projects/yoyo-shop-xxxx/agent/sessions/xxx-bdc6-e74e-27c4-xxxxxx"
}

Now that you know a bit about the data that we’re getting back from Dialogflow, it’s time to process it. We’ll first go through the very first thing that the user will usually ask about: “What products are available?” This is triggered by the available_yoyos intent. In this case, we simply return the names from the yoyos.json file. Note that fulfillmentText is required by Dialogflow as they’re still the one who will throw the data back to the user:

const names = yoyos.map(({ name }) => name);

return res.json({
    fulfillmentText: "The yoyos available are: " + names.join(', ')
});

Next, we go through the fulfillment for the next logical thing that the user would type: “Select Yoyo X”. This is where the user selects a specific yoyo from the list provided by the bot. This is triggered by the select_yoyo intent. The name of the yoyo is available in the parameters because “Yoyo X” in “Select Yoyo X” is resolved as the @yoyos entity. Behind the scenes, Dialogflow already maps the exact name of the yoyo based on what the user has typed. This is done with the help of the synonyms that we’ve added to each reference value in the @yoyos entity:

if (parameters.yoyos && parameters.yoyos.length) {
  return res.json({
    fulfillmentText: `What would you like to know about ${parameters.yoyos}?`
  });
}

Lastly, we add the condition for fulfilling the user’s request for a specific data about the item that they picked. This is triggered by the *learn_more intent which we’ve added earlier. Since we’ve set yoyo as an output context in the select_yoyo intent earlier, the name of the yoyo becomes available under outputContexts and we can use it to find the specific data that the user is looking for:

else if (parameters.specs) {
  const yoyo_id = outputContexts[0].parameters.yoyos.replace(/ /g, '');
  const spec = parameters.specs;

  let spec_value = yoyos.find(item => item.id == yoyo_id)[spec];
  let payload = { is_url: false };
  if (spec == 'image') {
    spec_value = `${BASE_URL}/images/${spec_value}`
    payload = {
      is_url: true
    }
  }

  return res.json({
    fulfillmentText: spec_value,
    payload
  });
}

Running the App

At this point, we’re now ready to run the app.

Start by running the server with nodemon:

$ cd server
$ nodemon server.js
$ ~/ngrok http 5000

We need nodemon because we have to put the ngrok HTTPS URL as a config on the file:

const BASE_URL = 'YOUR NGROK HTTPS URL';

Next, run the app:

react-native run-android

OR

react-native run-ios

The final step is to let Dialogflow know about the server. On your agent’s dashboard, click on the Fulfillment menu on the sidebar. It will show the following UI. Enter your ngrok HTTPS URL, enable it, then save the changes:

You can also set up basic authentication and add custom headers here, but we’ll just skip it on this tutorial. If you use Dialogflow on production, be sure to add the necessary security measures so that not just anyone can use your server.

Final Thoughts

In this tutorial, you learned how to use Dialogflow in React Native to create an ecommerce chatbot. Specifically, you learned about the basic Dialogflow concepts and how to set up the entities, intents, and fulfillment.

You can find the complete code of the app on this GitHub repo.