We're hiring
Products

Channels

Beams

Chatkit

DocsTutorialsSupportCareersPusher Blog
Sign InSign Up
Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Sign InSign Up

How to build a weather chatbot with React and Dialogflow

  • Ayooluwa Isaiah
August 23rd, 2018
You will need Node 6+ and npm installed on your machine. Basic familiarity with Node and React will be helpful.

Dialogflow is a platform that provides Natural Language Processing services for chatbots as well as one-click integration with most of the popular messaging platforms such as Facebook, Telegram, Twitter, Slack, and more.

In this tutorial, I'll show you how to build a weather bot with React and Dialogflow, while using Pusher to provide realtime responses to the user.

The source code for the completed application can be found on GitHub.

Prerequisites

Before you attempt this tutorial, make sure you know the basics of Node.js and React. You also need to have Node.js (version 6 and above) and npm installed on your machine to run the various commands that we’ll be using throughout this tutorial.

Getting started

There are two components to our application. A frontend where user will send and receive messages, and the server where the messages are processed by Dialogflow and weather information is retrieved.

Let’s begin by building the server component for our application.

Create a new directory on your filesystem. Inside the newly created directory, run the following command in the terminal to initialize the project with a package.json file.

    npm init -y

Next, run the command below to install all the dependencies that we’ll be using to setup the chatbot server:

    npm install body-parser express dialogflow pusher dotenv node-fetch cors

Once the installation completes, create a new file server.js and paste in the following code to setup Express on port 5000. You can choose another port if 5000 is already in use on your machine.

    // server.js

    require('dotenv').config({ path: 'variables.env' });

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

    const app = express();

    app.use(cors());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));


    app.post('/chat', (req, res) => {
      const { message } = req.body;
      console.log(message);
    });

    app.set('port', process.env.PORT || 5000);
    const server = app.listen(app.get('port'), () => {
      console.log(`Express running → PORT ${server.address().port}`);
    });

The dotenv package is used to load environmental variables from a .env file into process.env Notice we are referencing a variables.env file at the top of the code. This is where we’ll store all our app credentials. You need to add this file to your .gitignore to prevent committing it into your git repo by accident.

Create the variables.env file at the root of your project directory and add a PORT variable within:

    // variables.env

    PORT=5000

Set up the React app

The most convenient way of getting a React app up and running with a single command is by using the create-react-app tool. You can install it with npm. Open your terminal and type this command:

    npm install -g create-react-app

Then run the command below from within your project directory to create the React application:

    create-react-app react-bot

The above command will create a new directory called react-bot and install React as well as its accompanying dependencies. It may take a while to complete, so sit tight and wait. Once it’s done, you should see a some information in the terminal informing you of what you can do next.

Next, cd into the react-bot directory and install the other dependencies which we’ll be needing for our app’s frontend:

    npm install pusher-js

Finally, start the development server by running yarn start from within the root of the react-bot directory.

Set up Pusher

Head over to the Pusher website and sign up for a free account. Select Channels apps on the sidebar, and hit Create Channels app to create a new app.

Once your app is created, retrieve your credentials from the API Keys tab, then add the following to your variables.env file:

    PUSHER_APP_ID=<your app id>
    PUSHER_APP_KEY=<your app key>
    PUSHER_APP_SECRET=<your app secret>
    PUSHER_APP_CLUSTER=<your app cluster>

Application logic

Within the react-bot folder, locate src/App.js and change its contents to look like this:

    // react-bot/src/App.js

    import React, { Component } from 'react';
    import Pusher from 'pusher-js';
    import './App.css';

    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          userMessage: '',
          conversation: [],
        };
      }

      componentDidMount() {
        const pusher = new Pusher('<your app key>', {
          cluster: '<your app cluster>',
          encrypted: true,
        });

        const channel = pusher.subscribe('bot');
        channel.bind('bot-response', data => {
          const msg = {
            text: data.message,
            user: 'ai',
          };
          this.setState({
            conversation: [...this.state.conversation, msg],
          });
        });
      }

      handleChange = event => {
        this.setState({ userMessage: event.target.value });
      };

      handleSubmit = event => {
        event.preventDefault();
        if (!this.state.userMessage.trim()) return;

        const msg = {
          text: this.state.userMessage,
          user: 'human',
        };

        this.setState({
          conversation: [...this.state.conversation, msg],
        });

        fetch('http://localhost:5000/chat', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            message: this.state.userMessage,
          }),
        });

        this.setState({ userMessage: '' });
      };

      render() {
        const ChatBubble = (text, i, className) => {
          return (
            <div key={`${className}-${i}`} className={`${className} chat-bubble`}>
              <span className="chat-content">{text}</span>
            </div>
          );
        };

        const chat = this.state.conversation.map((e, index) =>
          ChatBubble(e.text, index, e.user)
        );

        return (
          <div>
            <h1>React Chatbot</h1>
            <div className="chat-window">
              <div className="conversation-view">{chat}</div>
              <div className="message-box">
                <form onSubmit={this.handleSubmit}>
                  <input
                    value={this.state.userMessage}
                    onInput={this.handleChange}
                    className="text-input"
                    type="text"
                    autoFocus
                    placeholder="Type your message and hit Enter to send"
                  />
                </form>
              </div>
            </div>
          </div>
        );
      }
    }

    export default App;

Our application state is initialized with two values: userMessage which contains the value of whatever the user types into the input field, and conversation which is an array that will hold each message in the conversation.

The handleChange function runs on every keystroke to update userMessage, which allows the displayed value to update as the user types. handleSubmit is called when the user hits the Enter key. It updates the conversation state with the contents of the user's message and sends the message in a POST request to the /chat endpoint which we will soon setup in our app’s server component, before clearing the input field by setting the value of userMessage to an empty string.

In componentDidMount(), we’re listening for the bot-response event on the bot channel. We will the trigger this event on the server and pass the response of the bot through the event payload.

Don’t forget to replace the <your app key> and <your app cluster> placeholders with the appropriate details from your Pusher account dashboard.

Add the styles

Within the react-bot folder, locate src/App.css and change its contents to look like this:

    // react-bot/src/App.css

    html, body {
      font: 14px/1.21 'Helvetica Neue', arial, sans-serif;
      font-weight: 400;
      box-sizing: border-box;
    }

    *, *::before, *::after {
      box-sizing: inherit;
      margin: 0;
      padding: 0;
    }

    h1 {
      text-align: center;
      margin-top: 40px;
      margin-bottom: 40px;
    }

    .chat-window {
      width: 750px;
      margin: auto;
      border: 1px solid #d5d5d5;
    }

    .conversation-view {
      width: 100%;
      min-height: 600px;
      padding: 20px 40px;
      background-color: #e5ddd5;
    }

    .message-box {
      width: 100%;
      padding: 20px 20px;
    }

    .text-input {
      width: 100%;
      border: none;
      padding: 5px;
    }

    .chat-bubble {
      font-size: 20px;
      margin-bottom: 20px;
      width: 100%;
      display: flex;
    }

    .chat-bubble.ai {
      justify-content: flex-end;
    }

    .chat-bubble.ai .chat-content {
      background-color: #4c9689;
    }

    .chat-content {
      display: inline-block;
      padding: 8px 15px;
      background-color: #0747a7;
      color: white;
      border-radius: 10px;
    }

Right now, the weather bot should look like this:

Set up Dialogflow

Head over to the Dialogflow website and sign up for a free account.

Click on the Create Agent button. Give your agent a name and fill in the remaining fields, then hit the CREATE button. You will be redirected to the main page of the agent.

Go to your agent’s settings. Under Google Project, click on the service account name. This will open your Google Cloud Platform service account’s page.

You should see a Dialogflow Integration service account in the list of accounts. Click on the three dots menu on the right and select Create Key. Leave the file format as JSON and click Create. This will download a JSON file to your computer.

Open the JSON file with your favorite text editor. You only need two of the values: private_key and client_email. Store these values in variables.env before proceeding.

    // variables.env

    DIALOGFLOW_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n<KEY>\n-----END PRIVATE KEY-----\n"
    DIALOGFLOW_CLIENT_EMAIL=foo@<PROJECT_ID>.iam.gserviceaccount.com

Enable small talk

Dialogflow provides a neat feature that allows any bot to have simple conversations with users without writing any code. To enable this feature, click Small Talk on the sidebar and toggle it on.

Handle message requests

Create a new file process-message.js in the root of your project directory and add the following code therein:

    // process-message.js

    const Dialogflow = require('dialogflow');
    const Pusher = require('pusher');

    // You can find your project ID in your Dialogflow agent settings
    const projectId = '<your project id>'; //https://dialogflow.com/docs/agents#settings
    const sessionId = '123456';
    const languageCode = 'en-US';

    const config = {
      credentials: {
        private_key: process.env.DIALOGFLOW_PRIVATE_KEY,
        client_email: process.env.DIALOGFLOW_CLIENT_EMAIL,
      },
    };

    const pusher = new Pusher({
      appId: process.env.PUSHER_APP_ID,
      key: process.env.PUSHER_APP_KEY,
      secret: process.env.PUSHER_APP_SECRET,
      cluster: process.env.PUSHER_APP_CLUSTER,
      encrypted: true,
    });

    const sessionClient = new Dialogflow.SessionsClient(config);

    const sessionPath = sessionClient.sessionPath(projectId, sessionId);

    const processMessage = message => {
      const request = {
        session: sessionPath,
        queryInput: {
          text: {
            text: message,
            languageCode,
          },
        },
      };

      sessionClient
        .detectIntent(request)
        .then(responses => {
          const result = responses[0].queryResult;
          return pusher.trigger('bot', 'bot-response', {
            message: result.fulfillmentText,
          });
        })
        .catch(err => {
          console.error('ERROR:', err);
        });
    }

    module.exports = processMessage;

Don’t forget to update the projectId with your agent’s project id which can be found in the Dialogflow agent settings. Then import processMessage into server.js and change the console.log(message statement in the /chat route to processMessage(message).

    // server.js

    require('dotenv').config({ path: 'variables.env' });

    const express = require('express');
    const bodyParser = require('body-parser');
    const cors = require('cors');
    const processMessage = require('./process-message');

    const app = express();

    app.use(cors());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));

    app.post('/chat', (req, res) => {
      const { message } = req.body;
      processMessage(message);
    });

    app.set('port', process.env.PORT || 5000);
    const server = app.listen(app.get('port'), () => {
      console.log(`Express running → PORT ${server.address().port}`);
    })

Start your server with node server.js in the terminal and test the bot to confirm that everything works. You can send messages like “Morning”, or “How are you” and you will get replies from the bot. These responses are from the Small Talk feature we enabled previously.

Create intents

As our bot is a weather bot, it must be able to handle requests like “What’s the weather” for any city. We need a way to extract the city name from the user’s message so that we can get the weather for the correct place and send it right back to the user.

Locate the Intents option on the left, and click CREATE INTENT. You can name the intent detect-city. Under Training phrases, add a few expressions such as the ones displayed in the the screenshot below.

You can see that the city names are highlighted in yellow. To do this, highlight the city names with your cursor and classify it under @sys.geo-city, a built in entity for cities.

Under Action and parameters, make geo-city a required parameter as shown in the screenshot below. Make sure you save your changes by hitting the SAVE button at the top of the page or Ctrl-S on your keyboard.

Set up OpenWeatherMap

Visit openweathermap.org and sign up for a free account. Once your account is created, visit this page to retrieve your api key. Make sure you add it to variables.env:

    // variables.env

    OPENWEATHER_API_KEY=<your api key>

Create a new file weather.js in your project directory and paste in the following contents:

    // weather.js

    const fetch = require('node-fetch');

    const { OPENWEATHER_API_KEY } = process.env;

    const getWeatherInfo = city =>
      fetch(
        `http://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${OPENWEATHER_API_KEY}`
      )
        .then(response => response.json())
        .then(data => {
          const kelvin = data.main.temp;
          const celsius = Math.round(kelvin - 273.15);
          return celsius;
        })
        .catch(error => console.log(error));

    module.exports = getWeatherInfo;

getWeatherInfo expects a city name as an argument. This city name is passed on to openweathermap and the current temperature is retrieved.

Next, import getWeatherInfo at the top of process-message.js:

    // process-message.js

    const Dialogflow = require('dialogflow');
    const Pusher = require('pusher');
    const getWeatherInfo = require('./weather');

    ...

Then change processMessage to look like this:

    // process-message.js

    const processMessage = message => {
      const request = {
        session: sessionPath,
        queryInput: {
          text: {
            text: message,
            languageCode,
          },
        },
      };

      sessionClient
        .detectIntent(request)
        .then(responses => {
          const result = responses[0].queryResult;

          // If the intent matches 'detect-city'
          if (result.intent.displayName === 'detect-city') {
            const city = result.parameters.fields['geo-city'].stringValue;

            // fetch the temperature from openweather map
            return getWeatherInfo(city).then(temperature => {
              return pusher.trigger('bot', 'bot-response', {
                message: `The weather is ${city} is ${temperature}°C`,
              });
            });
          }

          return pusher.trigger('bot', 'bot-response', {
            message: result.fulfillmentText,
          });
        })
        .catch(err => {
          console.error('ERROR:', err);
        });
    };

You should stop your server with Ctrl-C and restart it with node server.js after making the changes. Send a few messages to your bot requesting for the weather of a place. It should work just fine.

Conclusion

That concludes my tutorial. You can improve upon this project by adding your own functionalities and styling to it. Don't forget to grab the complete source code in this GitHub repository.

Clone the project repository
  • Chat
  • JavaScript
  • Node.js
  • React
  • Channels

Products

  • Channels
  • Beams
  • Chatkit

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.