September 17, 2020

Build a chatroom app with nodejs

Olususi Oluyemi

Often referred to, by some, as a group chat, a chat room consist of at least, more than one participant with the intention of sharing information for a specific purpose. This could be a private or public chat room for participants to share fun and general messages. Building such feature into your web application requires some level of expertise and a lot of time to be successful.


CometChat was built to make such implementation or integration on a web application simple, robust and a hassle free task for you as a developer. Creating a group on CometChat is as simple as invoking method createGroup() from the CometChat class within its SDK or by making an API call that sends an HTTP request with the appropriate details as shown here.


In this tutorial, we will build a chat room and create users that will eventually become participants and therefore have the privilege to share messages within the group during a chat session. To keep things simple, we will make use of the default group that will be created by CometChat upon successful creation of an app from our CometChat dashboard. More about this later in the tutorial. Lastly, to enable the users of our application to participate, we will use the RESTful APIs of CometChat to automatically add them to a preferred group.


Technical Overview and Prerequisites

Our project will use Node.js and Vue.js for the backend and frontend respectively. The registration process will mandate the user to provide a unique username and password. Afterward, we will persist these details in our database and return a successful response to the frontend. Next, an API call will be made to CometChat RESTful API to also create such user on CometChat app, generate an authentication token and also include them in a group.


For a proper follow-up, you will need the following:

Cloning the Starter Repository

To begin, we are going to build on a starter project on GitHub, which already contains the basic structure for a Vue.js application. Use Git to clone this repository by issuing the following command from the terminal:

{% c-line %}git clone https://github.com/yemiwebby/chat-room-node-starter.git{% c-line-end %}

This will install the starter project in a chat-room-node-starter directory in your development folder. Some important folders and files in the starter project include:

  • views: This folder contains the Register.vue, Login.vue, Chat.vue to handle registration process, logging in and structuring the chat page respectively.
  • router: Here, we have defined each endpoints and referenced all the views created within the application.


Other files includes:

  • App.vue: This is the root component of the application. We will add more content to this later.
  • main.js: This file will be responsible for initializing the root component into an element with an id of app within the application.

Alongside other important dependencies, the starter project also contains the CometChat Pro SDK.

Now, move into the project folder and run the following command from the terminal to install the project’s dependencies:


{% c-block language="c" %}
// Move into the project
cd chat-room-node-starter
{% c-block-end %}


{% c-block language="c" %}
// install dependencies
npm install
{% c-block-end %}


Once the installation process is completed. Run the application using the following command:

{% c-line %}npm run serve{% c-line-end %}




This will render the Login page with links to view the registration and chat page. As a result of this, we are confident that our starter application contains the right code, to begin with. Then, we will start adding more code to make the group chat application functional.


Handling Authentication within the application

It is worth mentioning that utilize CometChat as a tool for handling chat integration within a particular web application would not change the expected authentication and authorization process. Authentication will be handled traditionally by registering a user and persisting their details in the database. The persisted details will then be checked against the credentials that will be provided by the user when logging in.


For this tutorial, once we register a user within our application, we will make use of the CometChat Create user API to create such user on CometChat. To log the user in, we will use the generated Auth Token specified for such user during the registration process. Consider this other method as the advanced authentication procedure for users on CometChat.


Create CometChat account

If you are yet to create a CometChat account, click here to set up a free CometChat Pro account. Once you are done, create a new CometChat app from your dashboard by clicking on the Add New App button:


Enter the desired name for your application, select a region, technology, and a use case. Once you are done, click on the Add App button to complete the process.


This will automatically spin up a new application for you on CometChat. Depending on the technology selected from the previous step, CometChat will make provision for a quick start guide to facilitate integrating CometChat inside your app or website with ease. You can check it out and go for your preferred option.


Setting up the Node.js server

Create a new folder named server within the root of the project. Then, create two new files named index.js and db.js inside of it. These files will house the business logic for authentication process and database configuration respectively. Before we start working the backend API, issue the command below to install the required dependencies for the server:

{% c-line %}npm install --save express body-parser bcrypt axios mysql sqlite3{% c-line-end %}

  • express: A fast simple web framework for Node.js
  • body-parser: A Node.js middleware for handling encoded form data.
  • bcrypt: A JavaScript library to help hash passwords within a Node.js application
  • axios: This is a promise based HTTP client used for sending HTTP requests within Node.js application
  • mysql: A node.js driver for MySQL, written in JavaScript.
  • sqlite3: This is an SQLite client for Node.js applications with SQL based migrations API.


Having installed all the dependencies from the command, open server/index.js and import the required modules

{% c-block language="javascript" %}
"use strict";
const express = require("express");
const DB = require("./db");
const bcrypt = require("bcrypt");
const bodyParser = require("body-parser");
const db = new DB("sqlitedb");
const app = express();
const router = express.Router();
router.use
(bodyParser.urlencoded({ extended: false }));
router.use
(bodyParser.json());
{% c-block-end %}


Here, we required all the packages we need for this application, defined the database and created an Express server.


Since we intend to allow the backend and frontend of this application run on separate ports, add the following code to allow cross domain communication:

{% c-block language="javascript" %}
// CORS middleware
const allowCrossDomain = function(req, res, next) {
 res.header
("Access-Control-Allow-Origin", "*");
 res.header
("Access-Control-Allow-Methods", "*");
 res.header
("Access-Control-Allow-Headers", "*");
 next
();
};
app.use
(allowCrossDomain);
{% c-block-end %}


Next, paste the following code to define routes for registering a new user and logging in users within our application:

{% c-block language="php" %}
router.post
("/register", function(req, res) {
db.insert(
   [req.body.username, bcrypt.hashSync(req.body.password, 8)],
   function(err) {
     console.log(err);
     if (err) {
       return res
         .status(500)
         .send("There was a problem registering the user.");
     }
     db.selectByUsername(req.body.username, (err, user) => {
       if (err) {
         return res.status(500).send("There was a problem getting user");
       }
       res.status(200).send({ auth: true, user });
     });
   }
 );
});
router.post
("/login", (req, res) => {
 db.selectByUsername(req.body.username, (err, user) => {
   if (err) {
     return res.status(500).send("Error on the server.");
   }
   if (!user) {
     return res.status(404).send({ auth: true, message: "No user found." });
   }
   let passwordIsValid = bcrypt.compareSync(req.body.password, user.password);
   if (!passwordIsValid) {
     return res.status(401).send({ auth: false });
   }
   res.status(200).send({ auth: true, user: user });
 });
});
{% c-block-end %}



For the first route ( i.e /register ), we retrieved the username and password sent during the registration process and inserted into the database using a required database method that we will defined later in the tutorial.


Next, the /login route receives the user credentials which contain the username and password for authentication purposes. We checked if the username exists in the database and confirmed if the password entered is valid.


The last route that we need to define is for persisting the unique generated auth token to a user on CometChat into the database. For that purpose, add the code below:


{% c-block language="php" %}
router.post
("/update/token", function(req, res) {
 db.selectByUsername(req.body.uid, (err, user) => {
   if (err) {
     return res.status(500).send("Error on the server.");
   }
  if (!user) {
     return res.status(404).send("No user found.");
   }
   db.updateToken([req.body.token, req.body.uid], function(err) {
     if (err) {
       return res.status(500).send("There was a problem update user token.");
     }
   });
   res.status(200).send({ success: true });
 });
});
{% c-block-end %}


Finally, add the following code to make the backend of our application available on port 3000.


{% c-block language="php" %}
app.use
(router);
let port = process.env.PORT || 3000;
let server = app.listen(port, function() {
 console.log
("Express server listening on port " + port);
});
{% c-block-end %}


Creating the database module

In the preceding section, we required a DB module that was used within all the routes created so far. To implement the logic responsible for the database interaction, open the server/db.js file and paste the following content in it:


{% c-block language="javascript" %}
"use strict";
const sqlite3 = require("sqlite3").verbose();
class Db {
 constructor(file) {
   this.db = new sqlite3.Database(file);
   this.createTable();
 }
 createTable() {
   const sql = `
           CREATE TABLE IF NOT EXISTS user (
               id integer PRIMARY KEY,
               username text UNIQUE,
               password text,
               token text DEFAULT NULL)
`;
   return this.db.run(sql);
 }
 selectByUsername(username, callback) {
   return this.db.get(
     `SELECT * FROM user WHERE username = ?`,
     [username],
     function(err, row) {
       callback(err, row);
     }
   );
 }
 updateToken(data, callback) {
return this.db.run(
     `UPDATE user SET token = ? WHERE username = ?`,
     data,
     (err) => {
       callback(err);
     }
   );
 }
 insert(user, callback) {
   return this.db.run(
     "INSERT INTO user (username,password) VALUES (?,?)",
     user,
     (err) => {
       callback(err);
     }
   );
 }
}
module.exports = Db;
{% c-block-end %}


The purpose of the class defined here is to abstract the functions to carry out specific database interaction within our application. This eliminates repetition of code and make our functions reusable. This concludes the backend of our application, we will update the frontend part in the next section.


Update environment file

To complete the structure for the frontend, begin by creating a .env file and use the following content for it:


{% c-block language="javascript" %}
VUE_APP_COMMETCHAT_API_KEY=YOUR_REST_API_KEY        
VUE_APP_COMMETCHAT_APP_ID=YOUR_COMMETCHAT_APP_ID
VUE_APP_COMMETCHAT_GUID=supergroup
VUE_APP_COMMETCHAT_REGION=YOUR_COMMETCHAT_APP_REGION
{% c-block-end %}


Replace the placeholders with the appropriate details obtained from your CometChat dashboard. Please be sure to use the value of your REST API Key instead of the Auth Key:




Initialize CometChat App

Next, open src/main.js file and replace its content with:

{% c-block language="javascript" %}
import
Vue from "vue";
import
App from "./App.vue";
import
router from "./router";
import
{ CometChat } from "@cometchat-pro/chat";
import
Axios from "axios";
Vue.prototype.$http = Axios;
Vue.config.
productionTip = false;
const { VUE_APP_COMMETCHAT_APP_ID, VUE_APP_COMMETCHAT_REGION } = process.env;
let cometChatAppSetting = new CometChat.AppSettingsBuilder()
 .subscribePresenceForAllUsers()
 .setRegion(VUE_APP_COMMETCHAT_REGION)
 .build();
CometChat.init
(VUE_APP_COMMETCHAT_APP_ID, cometChatAppSetting).then(
 () => {
   console.log("Initialization completed successfully");
   new Vue({
     router,
     render: (h) => h(App),
   }).$mount("#app");
 },
 (error) => {
   console.log("Initialization failed with error:", error);
 }
);
{% c-block-end %}

One of the key concepts when working with CometChat SDK is to initialize it at the start of the application by calling the init() method. This is what we have done within the code snippet defined above.

Creating views and Cloning the UI Kit for Vue

Here, we will start by creating the view files required by our application. This chat application will need to authenticate users and once the authentication process is successful, the user will be redirected to a chat page where they can interact with other participants. To avoid spending too much time and effort crafting an appealing user interface for our chat view, we will leverage the UI kit created by CometChat team here on GitHub to easily get started.


To integrate, clone the Vue chat UI kit into your application using the following command:

{% c-line %}git clone https://github.com/cometchat-pro/vue-chat-ui-kit.git{% c-line-end %}


Now open the UI Kit source code with a code editor or any file explorer and copy the cometchat-components folder. Then create a new folder named lib within the src folder of your project and paste the cometchat-components in it. With this in place, you can then easily reference and make use of some of the custom UI component to easily set up views for your application.


Chat view

At the moment, the content and structure of the chat view is basic and non-functional. We will change that by replacing its content with the following:


{% c-block language="javascript" %}
<template>
 <div>
   <div class="page-wrapper">
     <div
       class="page-int-wrapper"
       id="pageWrapper"
       :class="{
         'left-open': leftOpen,
         'center-open': centerOpen,
         'right-open': rightOpen,
       }"
     >
       <div class="ccl-left-panel">
         <GroupList />
       </div>
       <MessageContainer :currentUser="currentUser" />
       <RightSidebar />
     </div>
   </div>
 </div>
</template>
{% c-block-end %}


Here, we created a structure that brings together different UI components to represent and build a chat view of our application. Each of the components referenced here is from the UI kit downloaded earlier.


To import these components and the appropriate logic for the application, use the following content for the <script></script> section within the file:


{% c-block language="javascript" %}
<script>
import
{ CometChat } from "@cometchat-pro/chat";
import
GroupList from "../lib/cometchat-components/components/GroupList";
import
MessageContainer from "../lib/cometchat-components/components/MessageContainer";
import
RightSidebar from "../lib/cometchat-components/components/RightSidebar";
export
default {
 components: {
   GroupList,
   MessageContainer,
   RightSidebar,
 },
 data() {
   return {
     currentUser: null,
     leftOpen: true,
     centerOpen: false,
     rightOpen: false,
   };
 },
 created() {
   CometChat.getLoggedinUser().then(
     (user) => {
       console.log("we are here", user);
       this.currentUser = user;
       if (!user) {
         this.$router.push({
           name: "home",
         });
       }
     },
     (error) => {
       console.log(error);
       this.$router.push({
         name: "home",
       });
     }
   );
 },
 mounted() {
   this.$root.$on("selectedUser", (data) => {
     console.log(data);
     const el = document.getElementById("pageWrapper");
     if (el.classList.contains("left-open")) {
       el.classList.add("center-open");
       el.classList.remove("left-open");
     }
   });
 },
};
</script>
{% c-block-end %}


First, we imported CometChat SDK and three different components from the cometchat-components folder:

  • GroupList: This component will list the number of groups that an authenticated CometChat app user belongs to. With that, a user can easily select a particular chat group he or she will like to participate in.
  • MessageContainer: This is a very important component for the chat view. It holds the logic to render different message types (Video, Audio, Media, Text) sent to a group or participants during a chat. It has a structure that houses the header of a chat view, the body, that is, the messages sent in a group and finally the form with an input fields where messages for a group chat are composed.
  • RightSidebar: This component house the sidebar on the right hand side of the chat view. It is hidden by default and for the sake of this tutorial, we won’t focus on it.


Next, we used the getLoggedinUser() method from the CometChat() class to retrieve the details of the currently logged in user.


Updating the AppComponent

Open the src/App.vue file and update its content by including a <script></script>  section as shown here:

{% c-block language="xml" %}
<script>
import
{ CometChat } from "@cometchat-pro/chat";
export
default {
 data() {
   return {
     user: null,
   };
 },
 created() {
   this.getLoggedInUser();
 },
 methods: {
   getLoggedInUser() {
     CometChat.getLoggedinUser().then((user) => {
       if (user !== null) {
         this.$router.push({
           name: "chat",
         });
       }
     });
   },
 },
};
</script>
{% c-block-end %}


From the snippet above, we used the getLoggedinUser() method from the CometChat SDK to retrieve the currently logged in user on our CometChat app. This will ensure that a currently authenticated user is redirected to the chat page.


Lastly, update the <style><.style> section of the same App.vue  file by importing the stylesheet from the cometchat  chat kit:


{% c-line %}@import "./lib/cometchat-components/assets/css/style.css";{% c-line-end %}


Add Script to run the Node Server

Let’s add a script to make running the node server easier. Open the package.json file and update it by including the line below:

{% c-block language="javascript" %}
 "scripts": {
   ...
   "backend": "node server/index"
 },
{% c-block-end %}

Testing the application

Ensure that you have the project opened from two different terminal and start the Node.js backend using the following command:

{% c-line %}npm run backend{% c-line-end %}

If you Vue.js application is not running, you can go ahead start it with:

{% c-line %}npm run serve{% c-line-end %}


Register a user

Go ahead and register as a new user. You can create two separate accounts for testing purposes:




Log in and start a chat

Now, log in and select a group from the list on the sidebar to start a chat. You will have a page similar to the one depicted by this image:



Conclusion

We started this tutorial with a minimal non functional Vue.js application and gradually added more code to make it a properly built chat room where participants can share messages irrespective of their location. Looking at the current state of work in the world and the wide adoption of remote work by most tech companies, this feature will go a long way to improve interactions for your app and can also be used to communicate with your tech team.

The complete source code can be found here on GitHub. Please feel free to clone it and update its existing features.