Real-Time Messaging App with Node

Ayooluwa I.
Ayooluwa I.
Published March 31, 2020 Updated August 28, 2020

This tutorial will take you through how to create a live chat app that responds in real-time, with the help of Dialogflow and Stream’s Messaging Platform.

We’ll be making a chatbot that interprets the intent of a user’s input using Natural Language Processing (NLP) and responds appropriately, giving the information it has gathered.

Prerequisites

You'll need to have recent versions of Node.js and yarn installed on your machine before proceeding with this tutorial. A working understanding of React is also helpful, as we’ll be making use of Stream’s React Components to flesh out our application interface.

Finally, make sure to have Ngrok installed on your computer; we’ll be using it to expose our local server to the web so that we can watch for new messages in our application.

Creating a New React Application

To start building out your application, open your terminal, and navigate to the directory where you normally keep your coding projects. Next, run the command below to bootstrap a new React application using create-react-app:

sh
1
$ npx create-react-app live-chat

Once the command has finished running, cd into your live-chat directory, and run yarn start, to start your app on port 3000.

Signing Up for Stream

Since we'll be making use of many of Stream's chat components, we'll need an account with unique keys, just for us; create a new Stream account, or sign in to your existing Stream account to get the ball rolling. Once you’re logged in, you can use the credentials from the app that's automatically created for you. Or, if you prefer, go to the dashboard, hit the blue “Create App” button at the top right of the screen to create a new app, scroll to the bottom of your dashboard, and take note of the application access keys which will be presented to you on creation:

Stream Dashboard

Signing Up for Dialogflow

Head over to the Dialogflow website and sign up for a free account. Next, create a new agent by clicking the blue Create Agent button in the sidebar.

Once your agent is created, click the Small Talk option in the sidebar and enable it for your bot; this option allows your bot to respond to common greetings immediately, without any further work on your part:

Enable small talk in Dialog flow

The next step is to grab your agent keys, which you can do by heading over to your agent’s settings. Under Google Project, click on the service account name. This will open the Service Accounts page for your Google Cloud Platform.

Find the Dialogflow Integrations entry in the Service Accounts table, and click the three dots menu on the right. Then, click the Create Key option and download your credentials in JSON format:

Creating the Live Chat Server

Let’s go ahead and set up our live chat server! Before we write any code, let’s install the additional dependencies we’ll be needing:

sh
1
$ yarn add express body-parser cors dotenv stream-chat

Next, set up a new .env file in the root of your project directory and add your Stream and Dialogflow credentials as shown below:

PORT=5500
STREAM_API_KEY=<your stream app key>
STREAM_APP_SECRET=<your stream app secret>
DIALOGFLOW_PRIVATE_KEY="<your dialogflow private key>"
DIALOGFLOW_CLIENT_EMAIL=<your dialogflow client email>

Note that your Dialogflow private key and client email are found in the JSON file you downloaded in the previous section. Also, note that only the private key is enclosed in quotes.

Now that we've set up our packages and environment variables, create a new server.js file in the root of your project directory, and paste in the following code:

require('dotenv').config();

const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const { StreamChat } = require('stream-chat');

const app = express();

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

// initialize Stream Chat SDK

const serverSideClient = new StreamChat(
  process.env.STREAM_API_KEY,
  process.env.STREAM_APP_SECRET
);

app.post('/join', async (req, res) => {
  const { username } = req.body;
  const token = serverSideClient.createToken(username);
  try {
    await serverSideClient.updateUser(
      {
        id: username,
        name: username,
      },
      token
    );
  } catch (err) {
    console.log(err);
  }

  const admin = { id: 'admin' };
  const channel = serverSideClient.channel('commerce', 'live-chat', {
    name: 'Live Chat',
    created_by: admin,
  });

  try {
    await channel.create();
    await channel.addMembers([username, 'admin']);
  } catch (err) {
    console.log(err);
  }

  return res
    .status(200)
    .json({ user: { username }, token, api_key: process.env.STREAM_API_KEY });
});

const server = app.listen(process.env.PORT || 5500, () => {
  const { port } = server.address();
  console.log(`Server running on PORT ${port}`);
});

Here, we have a single /join route that expects a username from the client-side and creates an authentication token for the user. The updateUser() method creates the user on our Stream chat instance, passing in the token for the user. If the user already exists, the method only updates the properties on the user.

After creating the user, we initialize a of the type commerce, whose id is set to live-chat. "commerce" is one of the five default channel types on Stream and is aimed at providing good defaults for building an app similar to Intercom or Drift.

The create() method creates a new channel and or returns a channel with the set id, if it exists already. Finally, the user is added as a member of the "live-chat" channel, and the authentication token and api_key are sent to the client. This is necessary for authenticating users on the application frontend.

That’s all the code we need on the server to get our live chat application working! Run node server.js in the terminal to start the server on port 5500 before moving on to the next section.

Building the Application Interface

In order to create our application frontend, we'll need to install two additional packages: stream-chat-react and axios. The former is the official set of React components for Stream Chat, which removes all the hassle of creating a complex chat UI, while the latter is a popular library for making requests in both browser and Node.js environments.

Install them both with the command below:

sh
1
$ yarn add stream-chat-react axios
Building your own app? Get early access to our Livestream or Video Calling API and launch in days!

Next, open up src/App.js in your text editor, and replace its contents with the following code:

import React, { useState, useEffect } from 'react';
import './App.css';
import {
  Chat,
  Channel,
  ChannelHeader,
  Window,
  MessageList,
  MessageCommerce,
  MessageInput,
} from 'stream-chat-react';
import { StreamChat } from 'stream-chat';
import axios from 'axios';

import 'stream-chat-react/dist/css/index.css';

let chatClient;

function App() {
  const [channel, setChannel] = useState(null);

  useEffect(() => {
    const username = 'joker';
    async function getToken() {
      try {
        const response = await axios.post('http://localhost:5500/join', {
          username,
        });
        const { token } = response.data;
        const apiKey = response.data.api_key;

        chatClient = new StreamChat(apiKey);

        await chatClient.setUser(
          {
            id: username,
            name: username,
          },
          token
        );

        const channel = chatClient.channel('commerce', 'live-chat');
        await channel.watch();
        setChannel(channel);
      } catch (err) {
        console.log(err);
      }
    }

    getToken();
  }, []);

  if (channel) {
    return (
      <Chat client={chatClient} theme="commerce light">
        <Channel channel={channel}>
          <Window>
            <ChannelHeader />
            <MessageList Message={MessageCommerce} />
            <MessageInput focus />
          </Window>
        </Channel>
      </Chat>
    );
  }

  return <div></div>;
}

export default App;

Thanks to Stream’s React components, we can construct a functional chat interface in only 70 lines of code! At this point, you will be able to send messages in the channel, but you will not receive a response to any of them:

Example

Exposing your Server to the Web

At the moment, your Express server is only accessible on your machine. Before we can receive webhook events from Stream, we need to expose the server on the web, which we can do using ngrok.

Once you have installed ngrok, start an HTTP tunnel on port 5500 by running the following command in a separate terminal session:

sh
1
$ ./ngrok http 5000
Example - Live Chat

Setting Up your Webhook

We are now ready for webhooks! Follow the instructions on this page to set up Stream CLI, which makes it easy to create and manage your Stream Chat applications directly from the command line. Once you have them installed and initialized the CLI tool, run the command below to set up your webhook URL; make sure to substitute the ngrok URL below with your own:

sh
1
$ stream chat:push:webhook --url 'https://******.ngrok.io/webhook'

The implication of setting a webhook URL on your Stream application is that all events that occur in your application will be sent in a POST request to your webhook URL; we’ll set up the /webhook route on our in the next section.

Processing Incoming Messages with Dialogflow

Let's incorporate Dialogflow into our app! Create a new process-message.js file in the root of your project directory, and update its contents as follows:

const projectId = '<your dialogflow project id>';
const sessionId = '123456';
const languageCode = 'en-US';

const dialogflow = require('dialogflow');

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

const sessionClient = new dialogflow.SessionsClient(config);

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

module.exports = async event => {
  const message = event.message.text;

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

  const responses = await sessionClient.detectIntent(request);
  const result = responses[0].queryResult;
  return result;
};

The exported function above expects the message.new event from Stream, which is sent whenever a new message is sent in the application. The message text is subsequently sent to Dialogflow, which determines the intent of the text and returns the most relevant result that it finds.

Note that you should replace the <your dialogflow project id> placeholder above with your agent’s project ID, which can be retrieved from the downloaded JSON file or your Dialogflow dashboard.

You can find your agent’s project ID in the Dialogflow dashboard

To receive message events from Stream, you'll need to set up the /webhook route in your server.js file:

require('dotenv').config();

const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const { StreamChat } = require('stream-chat');
const processMessage = require('./process-message');

const app = express();

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

// initialize Stream Chat SDK

const serverSideClient = new StreamChat(
  process.env.STREAM_API_KEY,
  process.env.STREAM_APP_SECRET
);

app.post('/webhook', async (req, res) => {
  const { type, user } = req.body;
  try {
    if (type === 'message.new' && user.id !== 'admin') {
      const result = await processMessage(req.body);
      const channel = serverSideClient.channel('commerce', 'live-chat', {
        name: 'Live Chat',
      });

      await channel.sendMessage({
        text: result.fulfillmentText,
        user: {
          id: 'admin',
        },
      });

      res.status(200).end();
    }
  } catch (err) {
    console.log(err);
    res.status(500).end();
  }
});

app.post('/join', async (req, res) => {
  const { username } = req.body;
  const token = serverSideClient.createToken(username);
  try {
    await serverSideClient.updateUser(
      {
        id: username,
        name: username,
      },
      token
    );
  } catch (err) {
    console.log(err);
  }

  const admin = { id: 'admin' };
  const channel = serverSideClient.channel('commerce', 'live-chat', {
    name: 'Live Chat',
    created_by: admin,
  });

  try {
    await channel.create();
    await channel.addMembers([username, 'admin']);
  } catch (err) {
    console.log(err);
  }

  return res
    .status(200)
    .json({ user: { username }, token, api_key: process.env.STREAM_API_KEY });
});

const server = app.listen(process.env.PORT || 5500, () => {
  const { port } = server.address();
  console.log(`Server running on PORT ${port}`);
});

Once a matching response for the user’s message is received from Dialogflow, the response is promptly sent back to the Stream channel. You can test this out by restarting your server and sending a new message, such as a greeting. The responses you will receive are a result of the Small Talk feature we enabled earlier:

Enabling the Small Talk feature makes it possible for our chatbot to respond to basic messages

Creating Custom Intents

Intents allow you to map a user’s request with an appropriate response. For example, let’s say you are offering a Software as a Service (SaaS) tool, and a frequently asked question is if the tool integrates with WordPress; you can set up an "intent" for that query, and a provide a set of responses that would appropriately address the query.

Head over to your Dialogflow dashboard, and click Create Intent. You'll need to give your intent a name, such as "wordpress-integration". Next, under Training phrases, provide examples of how users may express their requests in natural language. The more phrases you add, the better your bot will be at detecting the correct intent:

Wordpress Dialogflow Integration

Finally, under Responses, add a few messages that will serve as appropriate replies when this intent is matched, and then save the new intent:

Training Phrases

Now, if you send a message to the bot that is similar to one of the training phrases, it should respond with one of the replies that you set up for the intent:

Default Dialogflow Response

Wrapping Up

In this article, we explored the process of creating a live chat application that provides real-time responses to customer requests, with the help of Dialogflow’s Natural Language Processing feature.

This tutorial only covers a simple use case. Still, you can build on what you learn here to create a fully-featured customer service chatbot that answers queries from customers, or even potential leads. Refer to the Stream documentation to discover other features that are available on the Stream platform!

Remember, you can grab the source code used for this tutorial on GitHub.

Thanks for reading and happy coding!

Integrating Video With Your App?
We've built a Video and Audio solution just for you. Check out our APIs and SDKs.
Learn more ->