Build a Vue chat app

Olususi Kayode Oluyemi
Vue.js Developers
Published in
15 min readJul 19, 2019

Today you’ll find a plethora of tools that enable you to embed chat in your apps however many of these tools suffer from the same downside namely, as developers they limit our flexibility and therefore creativity. Enter CometChat.

In this tutorial, I will teach you how to build a modern chat app using CometChat and the open-source CometChat JavaScript chat SDK. Taking advantage of CometChat will enable us to build a custom chat feature quickly and launch it to production with confidence.

Here is a preview of what we will build:

I encourage you to follow along but you’re also welcome to download and explore the chat example code on GitHub.

This application will allow users to log in from different browsers (or machines) and initiate a conversation by sending messages in realtime. We will be building a group chat application where users can all join a group (sometimes called a channel) and converse about whatever.

Getting started

To easily create a new Vue project, we will need to install a command-line interface made available for the Vue ecosystem to help facilitate the creation of a Vue project from the terminal. To start, run the command below to install the Vue CLI:

npm install -g @vue-cli

Once the process is completed, create a new Vue project for this tutorial using the vue command:

vue create vue-chat-app

Choose the “manually select features” options by pressing Enter on your keyboard and check the features you will be needing for this project by pressing space on your computer to select one. As shown below, you should select Babel, Router, and Linter / Formatter:

For other instructions, type y to use history mode for the router. Ideally, the default mode for Vue-Router is hash(#) mode as it uses the URL hash to simulate a full URL so that page won't be reloaded when the URL changes. Choosing history mode here will help to get rid of the hash mode to achieve URL navigation without a page reload and add this configuration to the router file that will be generated automatically for this project. Also, select ESLint with error prevention only to pick a linter / formatter config. Next, select Lint on save for additional lint features and save your configuration in a dedicated config file for future projects. Type a name for your preset, I named mine vuechats:

Once you are done with the configuration, Vue CLI will start the installation process of the new project named vue-chat-app and its dependencies.

Start the application

Run the new application by moving into it from the terminal and start the development server with:

// move into the app
cd vue-chat-app
// Start the server
npm run serve

Next, view the application on http://localhost:8080:

Install CometChat

Now that we have the application properly setup, we can now proceed to add the CometChat package to our application. This will enable our application access to interact with CometChat. To begin, stop the development server from running with CTRL + C from the terminal and run the following command afterward:

npm install @cometchat-pro/chat --save

After the installation of the CometChat, we can now reference the plugins and import the CometChat object to make use of it anywhere within our project as shown here:

import { CometChat } from '@cometchat-pro/chat'

Setup the CometChat Pro account, APP ID, API Key, and GroupID

A CometChat account is required before we can proceed to build an application that will leverage the awesome chat infrastructure made available by us at CometChat. Head over to the website and create a free CometChat Pro account. Fill in all the required information and you will have a trial account set up for you.

Log in to view your CometChat dashboard and let’s create a new project. This will give us access to a unique APP ID and an API Key

In the ‘Add New App’ dialog, enter a name and click the plus sign to create a new application. Once you are done, click on the Explore button for the new app created. You will be redirected to a new page as shown below:

Next, from the left side menu, go to “API Keys” tab and you will see a page similar to this:

Immediately after you created a new application from your dashboard, CometChat automatically generated an API Key for the new demo application for you. You don’t need to create a new one as this will suffice and give you full access to the functionality offered by CometChat. Don’t forget to note or better still, copy the automatically-generated Full access API Key and application ID as we will be needing them shortly.

Finally, click on the “Groups” tab to view the list of groups created for your App on CometChat:

From the image above, a group named Comic Heros Hangout with a GUID ****of supergroup was automatically created by CometChat immediately you created the new App earlier. This group contains the list of existing users such as superhero1, superhero2, superhero3 that you can log in with. You can also copy and save the GUID as it will be required when integrating with CometChat.

Now that we are done setting up all the necessary tools and credentials needed to successfully create our application, we will start building properly in a bit.

Initialize CometChat

Before calling any method from CometChat, we need to ensure that the SDK is aware that our application exists. You can do this by initializing CometChat with your ‘App ID’ generated for your application as obtained from the dashboard. This is the typical workflow and recommended by our team at CometChat. To begin, create a new file named .env within the root directory of the application and paste the following code in it:

// .env VUE_APP_COMMETCHAT_API_KEY=YOUR_API_KEY VUE_APP_COMMETCHAT_APP_ID=YOUR_APP_ID VUE_APP_COMMETCHAT_GUID=YOUR_GUID

This will make it easy to reference the application credentials within our project.

💡Note: Kindly replace YOUR_API_KEY , YOUR_APP_ID and YOUR_GUID placeholder with the credentials as obtained from your CometChat dashboard.

Now, open the root component of Vue.js application located in ./src/App.vue and replace its content with:

// ./src/App.vue <template>
<div id="app">
<router-view/>
</div>
</template>
<script>
import { CometChat } from "@cometchat-pro/chat";
import "./App.css";
export default {
created() {
this.initializeApp();
},
methods: {
initializeApp() {
const { VUE_APP_COMMETCHAT_APP_ID } = process.env
CometChat.init(VUE_APP_COMMETCHAT_APP_ID).then(
() => {
console.log("Initialization completed successfully");
},
error => {
console.log("Initialization failed with error:", error);
}
);
}
}
};
</script>

From the file above, we included the <router-view> functional component in order to render any matched component for a given path from Vue Router. We will configure the router later in this tutorial. Within the <script> section, we imported the CometChat object and a CSS file that contains some basic style for our chat app. Lastly, we initialize CometChat by passing the application ID as a parameter.

Add stylesheet and images

Here, you will create a stylesheet, add basic styles to improve the look and feel of the application and include the image assets that will be imported within the application. Begin by creating a new file named App.css within the ./src folder and paste the following content in it:

// ./src/App.css @import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
@import 'https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css';
@import url('https://fonts.googleapis.com/css?family=Abril+Fatface&display=swap');
@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');

* {
box-sizing: border-box;
}

body {
color: #333;
font-size: 13px;
margin: 0;
width: 100%;
height: 100vh;
-webkit-font-smoothing: antialiased;
}

h3 {
font-family: 'Abril Fatface';
margin-bottom: 20px;
}

button {
border: none;
width: 100%;
margin: auto;
margin-top: 40px;
cursor: pointer;
padding: 10px 0;
background: #1B47DB;
font-size: 14px;
color: #fff;
border-radius: 4px;
box-shadow: 0 4px 8px 0 #CFDFFF, 0 6px 20px 0 #CFDFFF;
font-weight: 700;
transform: perspective(1px) translateZ(0);
}...

What we have above is the truncated version of the CSS file. Copy the complete style from here on GitHub. In addition, delete the logo.png file found in ./src/assets folder, then download all the images here and save it back in the assets folder as we will need to make use of the images in all the pages within the application.

Login component

One of the key concepts when building chat applications with CometChat is to ensure that users are authenticated before they can have access to use CometChat and start a chat. To ensure this, we will create a Login component that will handle the logic for authenticating a user and redirecting such user to the appropriate page for a chat.

Locate the ./src/views/About.vue file, rename it to ./src/views/Login.vue and then replace its content with:

// ./src/views/Login.vue<template>
<div class="login-page">
<div class="login">
<div class="login-container">
<div class="login-form-column">
<form v-on:submit.prevent="authLoginUser">
<h3>Hello!</h3>
<p>Welcome to our little Vue demo powered by CometChat. Login with the username "superhero1" or "superhero2" and test the chat out.
To create your own user, see <a href="https://prodocs.cometchat.com/reference#createuser">our documentation</a> </p>
<div class="form-wrapper">
<label>Username</label>
<input type="text" name="username" id="username" v-model="username" placeholder="Enter your username" class="form-control" required>
</div>
<button type="submit">LOG IN &nbsp;&nbsp;<span v-if="showSpinner" class="fa fa-spin fa-spinner"></span> </button>
</form>
</div>
<div class="login-image-column">
<div class="image-holder">
<img src="../assets/login-illustration.svg" alt="">
</div>
</div>
</div>
</div>
</div>
</template>

<script>
import { CometChat } from "@cometchat-pro/chat";
export default {
data() {
return {
username: "",
showSpinner: false
};
},
methods: {
authLoginUser() {
var apiKey = process.env.VUE_APP_COMMETCHAT_API_KEY;
this.showSpinner = true;
CometChat.login(this.username, apiKey).then(
() => {
this.showSpinner = false;
this.$router.push({
name: "chat"
});
},
error => {
this.showSpinner = false;
alert(
"Whops. Something went wrong. This commonly happens when you enter a username that doesn't exist. Check the console for more information"
);
console.log("Login failed with error:", error.code);
}
);
}
}
};
</script>

Here, we have created the basic form with an input field that will accept the username of a user for authentication purposes.

Furthermore, within the <script> tag, we imported the CometChat object and created the authLoginUser() method attached to the form. Within the authLoginUser() method, we called the login() method from CometChat and passed our application API and the username to uniquely identify the user.

Once the user logs into the application, we used the Vue Router to redirect the user to the chat page to begin a chat.

Create NavBar and Spinner components

We will include two different reusable components:

  • NavBar: A navigation that will be displayed at the top of the chat page. This component will take the name and avatar of the authenticated user as props.
  • Spinner: A loading icon that will displayed to indicate an ongoing process, like posting a message during a chat.

To begin, delete the ./src/components/HelloWorld.vue file and create two new files within ./src/components named NavBar.vue and Spinner.vue.

NavBar component
Open./src/components/NavBar.vue and paste the following in it:

// ./src/components/NavBar.vue <template>
<div>
<nav>
<div class="nav-left-section">
<img class="logo" src="../assets/logo.svg">
<span id="title">Chat</span>
</div>
<div class="nav-right-section">
<span class="welcome-message">Welcome <b>{{ name }}</b> </span>&nbsp;
<img v-bind:src="avatar" class="avatar">
</div>
</nav>
</div>
</template>
<script>
export default {
props: ["name", "avatar"]
};
</script>

Here, we referenced the logo.svg file and also used the props value to display the name and avatar of a logged-in user.

Spinner Component
Open./src/components/Spinner.vue and use the following content for it:

// ./src/components/Spinner.vue<template>
<div class="spinner">
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg :width="size" :height="size" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#003">
<g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>
</div>
</template>
<script>
export default {
props: {
size: {
type: Number,
default: 60
}
}
};
</script>
<style scoped>
.spinner {
display: flex;
justify-content: center;
align-items: center;
}
</style>

The chat view

The chat view will house all the logic required to interact with the CometChat API to implement the chat functionality, which is the core feature of our application. Here, we will get the details of the logged-in user, implement a method to send and receive messages from a group chat and also fetch all previous messages sent by a user.

Start by renaming the ./src/views/Home.vue file to ./src/views/Chat.vue and replace its content with the following:

// ./src/views/Chat.vue <template>
<div class="booker">
<nav-bar :name="this.username" :avatar="this.avatar" />
<div class="chat">
<div class="container">
<div class="msg-header">
<div class="active">
<h5>#General</h5>
</div>
</div>

<div class="chat-page">
<div class="msg-inbox">
<div class="chats" id="chats">
<div class="msg-page" id="msg-page">

<div
v-if="loadingMessages"
class="loading-messages-container"
>
<spinner :size="100"/>
<span class="loading-text">
Loading Messages
</span>
</div>
<div class="text-center img-fluid empty-chat" v-else-if="!groupMessages.length" >
<div class="empty-chat-holder">
<img src="../assets/empty-state.svg" class="img-res" alt="empty chat image">
</div>

<div>
<h2> No new message? </h2>
<h6 class="empty-chat-sub-title">
Send your first message below.
</h6>
</div>
</div>

<div v-else>
<div v-for="message in groupMessages" v-bind:key="message.id">
<div class="received-chats" v-if="message.sender.uid != uid">
<div class="received-chats-img">
<img v-bind:src="message.sender.avatar" alt="" class="avatar">
</div>

<div class="received-msg">
<div class="received-msg-inbox">
<p><span>{{ message.sender.uid }}</span><br>{{ message.data.text }}</p>
</div>
</div>
</div>


<div class="outgoing-chats" v-else>
<div class="outgoing-chats-msg">
<p>{{ message.data.text }}</p>
</div>

<div class="outgoing-chats-img">
<img v-bind:src="message.sender.avatar" alt="" class="avatar">
</div>
</div>
</div>
</div>
</div>
</div>
</div>

<div class="msg-bottom">
<form class="message-form" v-on:submit.prevent="sendGroupMessage">
<div class="input-group">
<input type="text" class="form-control message-input" placeholder="Type something" v-model="chatMessage" required>
<spinner
v-if="sendingMessage"
class="sending-message-spinner"
:size="30"
/>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</template>

Here, once a user is authenticated and redirected to the chat page, we will receive the user details and display the unique username, avatar and also an empty chat page for users who just joined a new group with no previous messages.

Also included is a form with an input field that will be used by a user to send a message to a group. The form will be processed by a method that will be implemented later in this section named sendGroupMessage().

Get the logged in user details
We need to uniquely identify the current logged in user and to achieve that, we will retrieve the details of the user by calling a method named getLoggedInUser(). This will return a User object containing all the information related to the logged-in user. We will begin by adding this <script> section to our code. Place the contents below within the Chat.vue immediately after the closing tag of the <template> section:

// ./src/views/Chat.vue <script>
import { CometChat } from "@cometchat-pro/chat";
import NavBar from "../components/NavBar.vue";
import Spinner from "../components/Spinner.vue";

export default {
name: "home",
components: {
NavBar,
Spinner
},
data() {
return {
username: "",
avatar: "",
uid: "",
sendingMessage: false,
chatMessage: "",
loggingOut: false,
groupMessages: [],
loadingMessages: false
};
},
mounted() {
this.loadingMessages = true;
var listenerID = "UNIQUE_LISTENER_ID";

const messagesRequest = new CometChat.MessagesRequestBuilder()
.setLimit(100)
.build();
messagesRequest.fetchPrevious().then(
messages => {
console.log("Message list fetched:", messages);
console.log("this.groupMessages", this.groupMessages);
this.groupMessages = [...this.groupMessages, ...messages];
this.loadingMessages = false;
this.$nextTick(() => {
this.scrollToBottom();
});
},
error => {
console.log("Message fetching failed with error:", error);
}
);

CometChat.addMessageListener(
listenerID,
new CometChat.MessageListener({
onTextMessageReceived: textMessage => {
console.log("Text message received successfully", textMessage);
// Handle text message
console.log(this.groupMessages);
this.groupMessages = [...this.groupMessages, textMessage];
// console.log("avatar", textMessage.sender.avatar)
this.loadingMessages = false;
this.$nextTick(() => {
this.scrollToBottom();
});
}
})
);
},

created() {
this.getLoggedInUser();
},
methods: {
getLoggedInUser() {
CometChat.getLoggedinUser().then(
user => {
this.username = user.name;
this.avatar = user.avatar;
this.uid = user.uid;
},
error => {
this.$router.push({
name: "homepage"
});
console.log(error);
}
);
},
sendGroupMessage() {
this.sendingMessage = true;
var receiverID = process.env.VUE_APP_COMMETCHAT_GUID;
var messageText = this.chatMessage;
var messageType = CometChat.MESSAGE_TYPE.TEXT;
var receiverType = CometChat.RECEIVER_TYPE.GROUP;
let globalContext = this;

var textMessage = new CometChat.TextMessage(
receiverID,
messageText,
messageType,
receiverType
);

CometChat.sendMessage(textMessage).then(
message => {
console.log("Message sent successfully:", message);
this.chatMessage = "";
this.sendingMessage = false;
// Text Message Sent Successfully
this.groupMessages = [...globalContext.groupMessages, message];
this.$nextTick(() => {
this.scrollToBottom();
});
},
error => {
console.log("Message sending failed with error:", error);
}
);
},
scrollToBottom() {
const chat = document.getElementById("msg-page");
chat.scrollTo(0, chat.scrollHeight + 30);
}
}
};
</script>

Above, we imported the NavBar and Spinner component, including the CometChat method from the SDK. Also, we defined some properties and corresponding initial values within the data option. And finally, once the component is created, we called a method named getLoggedInUser() to automatically retrieve the details of a logged-in user and update the view alongside it.

At the moment, none of the members will be able to send a real-time message to the group yet, even after being authenticated. We will change this by adding methods to send group messages, listen for any new message posted to the group in realtime and fetch all previous messages.

Send new messages
To send a new message from our app to CometChat server, add a new method named sendGroupMessage() immediately after the getLoggedInUser() method:

// ./src/views/Chat.vue <script>
...
export default {
...
methods: {
...
sendGroupMessage() {
this.sendingMessage = true;
var receiverID = process.env.VUE_APP_COMMETCHAT_GUID;
var messageText = this.chatMessage;
var messageType = CometChat.MESSAGE_TYPE.TEXT;
var receiverType = CometChat.RECEIVER_TYPE.GROUP;
let globalContext = this;

var textMessage = new CometChat.TextMessage(
receiverID,
messageText,
messageType,
receiverType
);

CometChat.sendMessage(textMessage).then(
message => {
console.log("Message sent successfully:", message);
this.chatMessage = "";
this.sendingMessage = false;
// Text Message Sent Successfully
this.groupMessages = [...globalContext.groupMessages, message];
this.$nextTick(() => {
this.scrollToBottom();
});
},
error => {
console.log("Message sending failed with error:", error);
}
);
}
}
};
</script>

Sending messages to either a group or user on CometChat requires creating an object TextMessage class. Here, we instantiate this object and passed the mandatory parameters which are:

  • receiverID: This is to identify the recipient of the message, which in this case, is the GUID as obtained from the CometChat dashboard.
  • messageText: The text message that needs to be sent to the server
  • messageType: The type of message and
  • receiverType: The type of users that will receive the message.

Once the message is posted to the server, we updated the groupMessages property with the new message and added a method to scroll to the latest message. Add this scroll method after sendGroupMessage() function as shown below:

... 
scrollToBottom() {
const chat = document.getElementById("msg-page");
chat.scrollTo(0, chat.scrollHeight + 30);
}
...

Receive incoming messages and Fetch previous messages
To receive real-time incoming messages posted to a group by its participants, we would register an event listener and pass a unique listenerID to it. Also, CometChat SDK makes provision to fetch messages in bulk using the MessagesRequestBuilder class. We will add all these functionalities within a lifecycle hook in Vuejs called mounted. Go ahead and include the content below just before the created() lifecycle hook:

// ./src/views/Chat.vue <script>
...

export default {
...
mounted() {
this.loadingMessages = true;
var listenerID = "UNIQUE_LISTENER_ID";

const messagesRequest = new CometChat.MessagesRequestBuilder()
.setLimit(100)
.build();
messagesRequest.fetchPrevious().then(
messages => {
console.log("Message list fetched:", messages);
console.log("this.groupMessages", this.groupMessages);
this.groupMessages = [...this.groupMessages, ...messages];
this.loadingMessages = false;
this.$nextTick(() => {
this.scrollToBottom();
});
},
error => {
console.log("Message fetching failed with error:", error);
}
);

CometChat.addMessageListener(
listenerID,
new CometChat.MessageListener({
onTextMessageReceived: textMessage => {
console.log("Text message received successfully", textMessage);
// Handle text message
console.log(this.groupMessages);
this.groupMessages = [...this.groupMessages, textMessage];
// console.log("avatar", textMessage.sender.avatar)
this.loadingMessages = false;
this.$nextTick(() => {
this.scrollToBottom();
});
}
})
);
},
}
};
</script>

Once a user logs in, we will use the MessagesRequestBuilder to fetch any previous messages from the group and update the view with it.

In case you miss anything, the complete contents of the Chat.vue file can be found here.

Now, in order to render the component for each given path, you will update the generated router file within the project. Open the router file located in ./src/router.js and replace its content with the following:

// ./src/router.js import Vue from 'vue'
import Router from 'vue-router'
import Chat from './views/Chat.vue'
import Login from './views/Login.vue'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'homepage',
redirect: 'login'
},
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/chat',
name: 'chat',
component: Chat
}
]
})

What we have done here is to map each route within our application to the respective component that will handle the logic. This is a basic format of configuring Vue Router for a Vue.js application. Click here to find out more about Vue Router.

Test the application

If the application is currently running, you can navigate to the http://localhost:8080 to view the application from any of your favorite browsers. If otherwise, start the application by running npm run serve from the command line within the root directory of your project:

Next, open the application on two different browsers and log in as separate users. You can log in with superhero1 and superhero2 as shown here:

Once you can log in from both window, you will see a page similar to this:

Now that you have logged in with the details of two different users, you can now start a chat by typing a message in the input field and hit the Enter key on your computer to post your message and view it in realtime.

Conclusion

In this tutorial, you built a chat application with an appealing user interface and a proper structure. As shown in this tutorial, leveraging CometChat makes it seamless to put together an easy to customize chat application in no time.

This takes away the difficulties involved with the technicality of setting up your chat application infrastructure from scratch. You can easily extend the functionalities of this application and add more features.

I hope you found this tutorial helpful? Don’t forget to check the official documentation of CometChat Pro to explore further. Find the complete source code of this tutorial here on GitHub.

Originally published at https://www.cometchat.com on July 19, 2019.

--

--