Back to search

How to build an HTML chatbox

  • Chimezie Enyinnaya
August 3rd, 2018
You will need Node 8+, npm 3+ and git installed on your machine.

As the internet keeps evolving, the need to have a kind of chat system keeps increasing; whether it's a chat support system or even a full blown chat app. In this tutorial, I’ll be show you how to build a HTML chatbox with Chatkit and vanilla JavaScript.

What we'll be building

We'll be building a chatbox, where users can join a chat room and chat with members of the room. Also, we'll be able to keep track of users online presence, that is whenever they join or leave the room.

Below is a demo of the final app:

chatkit-chatbox-html-demo

This app will be built mainly with vanilla JavaScript and Chatkit. Then to create and authenticate users, we'll create a simple Express server.

Prerequisites

This tutorial assumes the following:

  • Node.js (v8 and above) and NPM (v3 and above) installed on your computer.
  • Git installed on your computer.
  • A way to serve HTML files. We’ll be using http-server, which can be installed through NPM (npm install http-server).

What is Chatkit?

Chatkit is a hosted API that helps you build impressive chat features into your applications with less code. It offers you features like:

  • Group chat
  • One-to-one chat
  • Private chat
  • Typing indicators
  • "Who's online" presence
  • Read receipts
  • Photo, video, and audio messages

Using our cross-platform SDKs, all chat data is sent through our hosted API where we manage chat state and broadcast it to your clients:

chatkit-diagram

With Chatkit, you'll never have to worry about scale, reliability, or infrastructure, all that will be taken care of for you.

Getting started

To get started, we need to sign up for Chatkit. Once we are signed up, we need to create a Chatkit instance. We can do that by clicking the Create button on the Chatkit dashboard. You can name the instance whatever you like, but for the purpose of this tutorial, we’ll name it html-chatbox.

new-chatkit-instance

Take note of the instance credentials (instance locator and secret key) as we’ll be making use of them to interact with Chatkit.

Next, let’s create a room we’ll be using in this tutorial. We can create rooms programmatically from within our app, but to keep things simple we’ll use the Instance Inspector from the Chatkit dashboard. Follow the documentation on creating a user and room. Take note the room identify as we’ll be needing it later.

Creating the server

For creating users and authenticating them, we need to create a server. As already mentioned, we’ll be creating a simple Express server. Instead of creating the server completely from scratch, we’ll clone an Express boilerplate so as to speed things up:

    $ git clone https://github.com/pusher-community/express-boilerplate html-chatbox

Once that’s done, let’s delete the views directory as we won’t be needing it. Then update package.json as below:

    // package.json

    {
      "dependencies": {
        "@pusher/chatkit-server": "^0.12.1",
        "body-parser": "^1.18.2",
        "cors": "^2.8.4",
        "express": "^4.16.2"
      }
    }

First, we remove express-handlebars from the dependences, then we add the Chatkit Node.js SDK and CORS middleware.

Next, we install the dependences:

    $ cd html-chatbox
    $ npm install

With those installed, we can start building the server. Replace the content of server.js with the following:

    // server.js

    const express = require('express')
    const bodyParser = require('body-parser')
    const cors = require('cors')
    const Chatkit = require('@pusher/chatkit-server')

    const app = express()

    const chatkit = new Chatkit.default({
      instanceLocator: 'YOUR_INSTANCE_LOCATOR',
      key: 'YOUR_SECRET_KEY'
    })

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

    app.post('/users', (req, res) => {
      const { username } = req.body

      chatkit
        .createUser({
          id: username,
          name: username
        })
        .then(() => {
          res.sendStatus(201)
        })
        .catch(err => {
          if (err.error === 'services/chatkit/user_already_exists') {
            console.log(`User already exists: ${username}`)
            res.sendStatus(200)
          } else {
            res.status(err.status).json(err)
          }
        })
    })

    app.post('/authenticate', (req, res) => {
      const authData = chatkit.authenticate({
        userId: req.query.user_id
      })
      res.status(authData.status).send(authData.body)
    })

    const port = 3001

    app.listen(port, err => {
      if (err) {
        console.log(err)
      } else {
        console.log(`Running on port ${port}`)
      }
    })

Using the Chatkit Node.js SDK, we create a new instance of Chatkit passing to it our INSTANCE_LOCATOR and SECRET_KEY (replace these with your own keys). Once the /users endpoint is hit, we create a new user with the provided username as both the ID and the name. Then return a success status if everything works fine, otherwise we throw appropriate errors. The /authenticate endpoint is used to authenticate incoming users, so as to make sure they are who they say they are.

Creating the client

With the server done, let’s move to creating the client. Within the project’s root directory, create a new index.html file and paste the code below into it:

    <!-- index.html -->

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Chatbox</title>
      <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css">
    </head>
    <body style="padding-top: 100px; padding-bottom: 100px">
      <div class="container">
        <div class="row justify-content-center">
          <div class="col-md-8">
            <div class="card">
              <div class="card-body">
                <div id="join">
                  <form method="post" id="username-form">
                    <div class="form-group">
                      <div class='input-group'>
                        <input type='text' name='username' class="form-control" placeholder="Enter your username">
                        <div class='input-group-append'>
                          <button class='btn btn-primary'>Join</button>
                        </div>
                      </div>
                    </div>
                  </form>
                </div>
                <div id="chatbox" style="display: none">
                  <div class="row">
                    <div class="col-md-8">
                      <div class="card">
                        <div class="card-header">Chatbox</div>
                        <div class="card-body">
                          <dl id="messageList"></dl>
                          <hr>
                          <form id="sendMessage" method="post">
                            <div class='input-group'>
                              <input type='text' name='message' class="form-control" placeholder="Type your message...">
                              <div class='input-group-append'>
                                <button class='btn btn-primary'>Send</button>
                              </div>
                            </div>
                          </form>
                        </div>
                      </div>
                    </div>
                    <div class="col-md-4">
                      <div class="card">
                        <div class="card-header">Users Online</div>
                        <div class="card-body p-0">
                          <ul id="onlineUsers" class="list-group list-group-flush"></ul>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <script src="https://unpkg.com/@pusher/chatkit@0.7.12/dist/web/chatkit.js"></script>
      <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    </body>
    </html>

The page is divided into two sections: the form for entering a username and the actual chatbox. Only when a user has entered a username will the chatbox will be shown. So when a user enters a username and clicks the Join button, the chatbox will be shown and the username form will disappear. As you can see, we are using CSS to set the display of the chatbox to none.

Also, we add two scripts to the page. The first is the Chatkit JavaScript client SDK and Axios, which we’ll be using to make HTTP requests to our server.

Next, add the code below immediately before the closing </body> tag:

    <script>
      const usernameForm = document.getElementById('username-form')

      usernameForm.addEventListener('submit', (e) => {
        e.preventDefault()

        const username = e.target.username.value

        axios.post('http://localhost:3001/users', { username })
          .then(() => {
            document.getElementById('join').style.display = 'none'
            document.getElementById('chatbox').style.display = 'block'

            const tokenProvider = new Chatkit.TokenProvider({
              url: 'http://localhost:3001/authenticate'
            })

            const chatManager = new Chatkit.ChatManager({
              instanceLocator: 'YOUR_INSTANCE_LOCATOR',
              userId: username,
              tokenProvider
            })

            chatManager
              .connect()
              .then(currentUser => {
                currentUser.subscribeToRoom({
                  roomId: ROOM_ID,
                  messageLimit: 100
                })
              }).catch(error => console.error(error))
          }).catch(error => console.error(error))
      })
    </script>

First, we get the username form, then add an event listener for when the form is submitted. Once the form is submitted, we prevent the default form behavior (that is, don’t refresh the page). Then we make a POST request to the /users endpoint on our server, passing along the username entered. If the request is successful, we hide the username form and display the chatbox.

Next, we create a tokenProvider using the /authenticate endpoint of our server. Then we create chatManager instance locator, user ID and the tokenProvider. Using the chatManager, we connect to Chatkit. Once the connection is successful, we’ll have access to the current user. We then subscribe the current user to the room (replace ROOM_ID with your room ID) we created earlier. Setting messageLimit: 100 allows us to show users old messages (the last 100 messages). If we don’t want to show old messages, we can easily set messageLimit to 0.

Sending and receiving messages

Now that users can connect to our chatbox and subscribe to a room, let’s add sending and receiving of messages. To do that, update subscribeToRoom as below:

    currentUser.subscribeToRoom({
      roomId: ROOM_ID,
      hooks: {
        onNewMessage: message => {
          const { senderId, text } = message

          const messageList = document.getElementById('messageList')
          const messageUser = document.createElement('dt')
          const messageBody = document.createElement('dd')

          messageUser.appendChild(document.createTextNode(senderId))
          messageBody.appendChild(document.createTextNode(text))
          messageList.appendChild(messageUser)
          messageList.appendChild(messageBody)
        }
      },
      messageLimit: 100
    }).then(() => {
      const sendMessage = document.getElementById('sendMessage')

      sendMessage.addEventListener("submit", e => {
        e.preventDefault()

        const message = e.target.message.value

        currentUser.sendMessage({
          text: message,
          roomId: ROOM_ID
        })

        e.target.message.value = ''
      })
    }).catch(error => console.error(error))

We add a onNewMessage hook to the subscribeToRoom object. This hook will be triggered once a new message is sent. What we are doing within the hook is simply creating some HTML elements with the message sender ID and message text, then append the created HTML to the chatbox.

Once the chat text box is submitted, we prevent the default form behavior. Then we get the message entered into the text box. Using the current user object, we a sendMessage method passing to it the message content and the ID of the room (replace ROOM_ID with your room ID). Lastly, we clear out the text box.

Displaying users online

The last feature our chatbox will have is the ability to keep track of users online presence. With Chatkit, this can be easily achieved. To display users that are online, add the following code before the code for sending chat messages, that is, immediately before const sendMessage = document.getElementById('sendMessage'):

    ...
    currentUser.rooms[0].users.forEach(user => {
      if (user.presence.state === 'online') {
        addUserElement(user)
      }
    })
    ...

We get the first room of the current user (which is the general room we created), then get all the users that are subscribed to the room, which will be an array of users. Then we loop through the array of users. Each user object has a presence state, which can be either or online or offline. If the user is online we add the user to the list of users that are online. We are making use of a addUserElement function, which we’ll create shortly.

Next, let’s create the addUserElement function. Add the code below outside the usernameForm event listener, that is, immediately before </script> tag:

    function addUserElement(user) {
      const onlineUsers = document.getElementById('onlineUsers')
      const singleUser = document.createElement('li')

      singleUser.className = 'list-group-item'
      singleUser.id = user.id

      singleUser.appendChild(document.createTextNode(user.name))
      onlineUsers.appendChild(singleUser)
    }

You will notice we add the user’s ID as the id attribute of the element being created, that way it will be easier for us to remove the user element once the user leaves the chat.

Chatkit has a onUserCameOnline and a onUserWentOffline hooks, which we can use to know when a user joins a chat or leaves a chat respectively. So, all we have to do is listen for these hooks and act accordingly.

Add the code below inside the hooks object from earlier on:

    ...,
    onUserCameOnline: user => {
      if (currentUser.id !== user.id) {
        addUserElement(user)
      }
    },
    onUserWentOffline: user => {
      const singleUser = document.getElementById(user.id)
      singleUser.parentNode.removeChild(singleUser)
    },
    ...

Both hooks accept a user object (that is, the user that joined or left). For the onUserCameOnline hook, we add a new user to the list of users that are online if the user is not the current user. The onUserWentOffline hook, we simply get the element by the user’s ID and removes it from the list of users that are online.

Below is the complete script:

    <script>
      const usernameForm = document.getElementById('username-form')

      usernameForm.addEventListener('submit', (e) => {
        e.preventDefault()

        const username = e.target.username.value

        axios.post('http://localhost:3001/users', { username })
          .then(() => {
            document.getElementById('join').style.display = 'none'
            document.getElementById('chatbox').style.display = 'block'

            const tokenProvider = new Chatkit.TokenProvider({
              url: 'http://localhost:3001/authenticate'
            })

            const chatManager = new Chatkit.ChatManager({
              instanceLocator: 'YOUR_INSTANCE_LOCATOR',
              userId: username,
              tokenProvider
            })

            chatManager
              .connect()
              .then(currentUser => {
                currentUser.subscribeToRoom({
                  roomId: ROOM_ID,
                  hooks: {
                    onNewMessage: message => {
                      const { senderId, text } = message

                      const messageList = document.getElementById('messageList')
                      const messageUser = document.createElement('dt')
                      const messageBody = document.createElement('dd')

                      messageUser.appendChild(document.createTextNode(senderId))
                      messageBody.appendChild(document.createTextNode(text))
                      messageList.appendChild(messageUser)
                      messageList.appendChild(messageBody)
                    },
                    onUserCameOnline: user => {
                      if (currentUser.id !== user.id) {
                        addUserElement(user)
                      }
                    },
                    onUserWentOffline: user => {
                      const singleUser = document.getElementById(user.id)
                      singleUser.parentNode.removeChild(singleUser)
                    }
                  },
                  messageLimit: 100
                }).then(() => {
                  currentUser.rooms[0].users.forEach(user => {
                    if (user.presence.state === 'online') {
                      addUserElement(user)
                    }
                  })

                  const sendMessage = document.getElementById('sendMessage')

                  sendMessage.addEventListener("submit", e => {
                    e.preventDefault()

                    const message = e.target.message.value

                    currentUser.sendMessage({
                      text: message,
                      roomId: ROOM_ID
                    })

                    e.target.message.value = ''
                  })
                }).catch(error => console.error(error))
              }).catch(error => console.error(error))
          }).catch(error => console.error(error))
      })

      function addUserElement(user) {
        const onlineUsers = document.getElementById('onlineUsers')
        const singleUser = document.createElement('li')

        singleUser.className = 'list-group-item'
        singleUser.id = user.id

        singleUser.appendChild(document.createTextNode(user.name))
        onlineUsers.appendChild(singleUser)
      }
    </script>

Testing the chatbox

Start the server:

    $ node server.js

Should be running on http://localhost:3001.

Also, in a new terminal, start a dev server to serve the index.html file:

    $ http-sever

Here, I’m using http-server, you can use your preferred dev server.

The client should be accessible on http://127.0.0.1:8080

Tying it out should result into something similar to the GIF below:

Conclusion

In this tutorial, we have seen how to build an HTML chatbox with Chatkit and vanilla JavaScript. In addition to send and receiving messages, we also saw how to display and keep track of users online presence.

Chatkit is a powerful tool, we have only succeeded in scratching the surface of what it can do, check out the Chatkit website to find more about Chatkit and other awesome features it has.

The complete code for this tutorial is available on GitHub.

  • Chatkit

© 2018 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 28 Scrutton Street, London EC2A 4RP.