Build one-on-one chat in your Vue app

Building a chat app for the browser is not a very straightforward task. It requires you to learn some realtime technology (like socket.

Taha Shashtari • Apr 21, 2020

Building a chat app for the browser is not a very straightforward task. It requires you to learn some realtime technology (like socket.io) and how to manage connections between participants. Not only that, but also you need to make sure your connections are reliable so your app doesn't miss a message sent between users.

These are just a few challenges that you can face when implementing your own chat system. But luckily for us, CometChat comes to save us the headache. CometChat doesn't only solve these issues, it also provides us with more cool features like chat groups, user roles, and friends list.

In this tutorial, I'll teach you how to build your own one-on-one chat app in Vue from scratch. After you finish this tutorial, you should have something like this:

You can get the tutorial's source code from GitHub. The instructions on how to run it are included there. It's a good idea to run the demo before diving in to make sure you have the full picture of what we're going to build here.

Creating a new Vue project

Let's create a new Vue project using Vue CLI. If you don't have it installed on your machine yet, install it via npm like this:

To create a new project, run the following command:

This will ask you to pick a preset. Let's choose typical-spa for this tutorial.

After it's finished, go to the project's directory and run it using: npm run serve.

Now if you open the browser to http://localhost:8080, you should see this:

Great! Now we have a running Vue app. Now let's create and style all necessary pages, and then integrate CometChat into it.

Setting up the routes

Let's start with the router. Open src/router.js and replace everything there with this:

We'll have only two pages in this app, login and chat. The chat page is where the bulk of our work would be. The login page is just responsible for authenticating the user. And since the user should be authenticated before using the app, we're redirecting the homepage (path: '/') to the login page.

You can also see how we used the history mode in the router to get rid of the hash in the url.

We've told the router about our two pages, but we didn't create them yet. So remove any existing components from src/views and create Login.vue and Chat.vue.

Preparing the pages

Before we start implementing our pages, we need to modify our root component, App.vue, then load the fonts and images that we'll be using in this app.

Open src/App.vue and replace everything with this:

Nothing important to explain here — we just removed the *#nav* element and modified the CSS.

We'll use Roboto and ‌Abril Fatface fonts in this app. To load them, open public/index.html, and add this in the <head> section:

Finally, let's get the needed images from the demo's repo and add them to src/assets.

Implementing Login.vue

Let's first add the HTML code for this component:

Three things to notice here:

  1. v-model="username" — we're keeping track of the entered username in the username data property, which we'll create later.

  2. @submit.prevent="login" — when the user submits the login form, we should call a method named login(), which we don't have yet.

  3. When the user is currently logging in, we should disable the username input and the login button. Note how we do that in the code above using the loggingIn flag (which we'll create in a bit). So we're using :disabled="loggingIn" on the input and the button. We're also updating the button's text to “LOGGING IN...” when loggingIn is true.

Now let's write the JS code for this component:

We've defined our data properties, username and loggingIn. We've also added the login method, which we'll implement later.

Lastly, let's add the CSS code:

If you check the login page in the browser, you should see this:

Preparing Chat.vue

Here's how this page would look like in the end:

Instead of writing all the code into this single component, let's break the content of this component into multiple components. We'll have three components in this page: Navbar.vue, ChatSidebar.vue, and ChatMain.vue.

Let's create these components into src/components and put the following code into Chat.vue:

This is just the start for Chat.vue. We'll keep updating it as we're working on the app.

Implementing Navbar.vue

Open src/components/Navbar.vue, and add this to the HTML section:

We're here hardcoding the user name and the avatar. Once we have the real data available, we'll get back to this file and update it.

As you can see, we should log out the user if he or she clicked on the avatar — we haven't defined this method yet, but we'll do that later. When the user is currently logging out, we should display a loading indicator, <spinner>, in place of the avatar (that's what loggingOut flag is for).

Now let's add the JS code below the HTML section:

So we've defined the loggingOut flag and the logout method. Note that we'll implement the logout method later because we need to connect to CometChat for that.

Lastly, let's add the CSS code:

Before moving to the next section, let's add the Spinner component. So in src/components create a new file named Spinner.vue, and put the following into it:

After this, your http://localhost:8080/chat page should look like this:

Implementing ChatMain.vue

As always, let's start with the HTML code.

The main area of the chat can have one of the following three states:

  1. Loading messages state: when fetching previous messages between participants, we should show a loading view that tells users about that.

  2. Empty state: when messages are fetched but nothing was returned from the server, then we should display a message that tells the user that there are no messages between you the active contact yet.

  3. Has messages state: in this state, we display any existing messages between the logged-in user and the selected contact. We're displaying the messages in another component called ChatMessages, which we didn't create yet. But since we're at it, let's create src/components/ChatMessage.vue.

Below the messages section, we have the messages input area. We're keeping track of the currently entered text message inside messageText data property. If the user pressed the Enter key, we should send the message by calling the sendMessage method. And as the message is being sent, we should disable the text input and show a loading indicator. We can know that the message is being sent from the sendingMessage data property, which we'll create next.

Now let's add the JS and CSS code for this component:

Here's what you should see in your browser now:

Installing and initializing CometChat

Now we're ready to start integrating CometChat into our app. To do that, we first have to create a new pro account in CometChat. You can create it from app.cometchat.com/#/register.

For each app you build, you have to create a new app in CometChat. You can do that easily from the dashboard. Just enter the app name in the "Add New App" box, and hit the "+" button. In our example let's name it "one-on-one chat app".

Hold your horses ⚠️🐴!
To follow this tutorial or run the example source code you'll need to create a V1 application.

v2 will be out of beta soon at which point we will update this tutorial.

For this tutorial, we need to know two pieces of information from CometChat: the App ID and the API Key.

You can get the App Id from the dashboard below the name of the app you've just created.

And for the API Key, click on "Explore" in the app box. Then go to the "API Keys" tab from the left sidebar. Then copy the auth-only api key.

Instead of embedding those keys directly into our code, let’s store them as environment variables so we can easily use different ones for different modes (like development, test, and production). To do so, create a new .env.local in your project's root directory. Then add this to it:

Then just replace the values with yours.

Before we can use CometChat, we have to install it first. So, run this from your terminal:

We won't be able to use CometChat until we initialize it with our App ID. Since this is the first thing we need to do in the app, let's initialize it from src/main.js file.

So, open your main.js file, and import CometChat at the top, like this:

Then call CometChat.init() below that.

Note how we fetched our CometChat appId from our env variables using process.env.VUE_APP_COMETCHAT_APP_ID.

Authenticating the user

Before a user can use CometChat, he or she must be logged in using CometChat.login(username, apiKey) first.

It's important to note that logging in to CometChat is different from logging in to your app in general. CometChat doesn't handle user management. This authentication is only for allowing the user to access CometChat.

So in a real world example, we should authenticate the user as we would normally do (check if the email and the hashed password matches a record in the database, for example), then after that, we can log in that user to CometChat programmatically through CometChat.login.

As this is a demo app, we're going to make things simpler and use CometChat authentication as our app authentication as well.

Let's implement authentication by filling our login() method in Login.vue like this:

So we're using CometChat.login with the current value of the username input and our auth-only apiKey, which we stored in .env.local.

Now if the user was logged in successfully, we redirect him or her to the chat page using this.$router.push({ name: 'chat' }).

Note that you can test logging in using one of the following test users that CometChat provides us with: superhero1, superhero2, superhero3, superhero4, or superhero5.

Fetching the logged-in user's data

After the user is logged in, we should fetch his or her data so we can use them throughout the application.

We can do this using CometChat.getLoggedinUser. So, let's load the user's data when the chat page is created, which will be inside the created hook function in this case.

Open Chat.vue, and define the created function like this:

An important thing to note here is how we're storing the user's data. Instead of storing them inside a data property in Chat.vue, we are storing them inside all Vue instances. This will allow us to access the current user's data directly from any component using this.$currentUser without the needing to pass it as a prop.

Note that adding Vue instance properties is fine as long as you’re building a very small app or a demo. But for anything bigger than this, I would recommend using Vuex instead.

You can also see how we're setting loggedIn to true when data are fetched. You'll see why we need this later. But for now let's not forget to define it in the data list.

To complete this method, let's import CometChat and Vue at the top.

Update the Navbar with real data

Since we now have the logged-in user's data, let's update the name and the avatar in the nav bar to use them.

Go to src/components/Navbar.vue, and update the displayed name to be like this:

And the avatar, like this:

If you view the chat page in the browser, you'll see that the navbar is broken. This is expected because we're assuming that the current user's data should be available before the page is loaded. But this isn't true in our case since we don't know when CometChat.getLoggedinUser() will be resolved.

To fix this, we should not display the page until the user's data is available. We can do this by adding this check to the root element of the chat page.

Now it should be clear why we needed to create that loggedIn property.

If you check the browser now, it should work as expected.

Loading contacts

Our next step is to load the contacts that we can chat with. Like loading the current user's data, we'll load them from the created() hook function.

Here's the implementation of loadContacts:

The returned list contains the first 5 users (excluding the logged-in user) that you have in your CometChat app — you can see them in the dashboard.

After the list is fetched, we store the contacts inside the contacts data property. We also set the first contact in the list as the active user that we're chatting with. We store that inside activeContactUid, as you can see above.

This means we have to add those properties inside our data list.

Showing contacts in the sidebar

Now we have the contacts fetched, let's display them in the sidebar. Before we open the sidebar component, let's pass the contacts list and the active contact id through its props. So, update <chat-sidebar/> in Chat.vue like this:

Now add the following into src/components/ChatSidebar.vue:

Here's what we're doing here:

  • We're looping through the contacts list using v-for.

  • We mark a contact as active (currently selected) by adding the .active class to it. A contact is active if its id is the same as activeContactId.

  • If the contact item has .online class, we show that the user is currently online (green circle).

  • If we click on a contact, we set it as active by emitting select-contact event with the contact's id.

We've already listened for the select-contact event but we haven't defined the handler for it yet. So in Chat.vue, add this method:

Now you should be able switch between active contacts.

If you check your browser now, you should see the contacts displayed like this:

Sending messages

Now let's focus on sending messages to the currently selected contact. All messaging related code should go to ChatMain.vue. But before we implement the sendMessage method, we need to pass the active contact object to the ChatMain component.

Currently, we only have the active contact id, not the contact object itself. So let's create a computed property that returns the currently selected contact object based on what's in activeContactUid.

Let's add that computed property in Chat.vue like this:

Now let's pass it to <chat-main/> component.

And then accept it in ChatMain.vue.

Now we have the active contact available, let's implement the sendMessage method in ChatMain.vue.

To send a message in CometChat to a specific contact, we have to create a new text message using new CometChat.TextMessage(), and in that message we should specify the contact's id we want to send the message to, the message text, the message type, and the receiver type.

After we have that message, we send it through CometChat.sendMessage(message).

Note that after the message is sent, we add it to the local messages array so it gets displayed immediately.

Before you test this, let's define the scrollToBottom method.

If you try to send a message, you won't see it displayed on the main area because we haven't implemented ChatMessages.vue yet.

So open src/components/ChatMessages.vue, and add this:

To distinguish between the sent and received messages, we add either an .outgoing or .incoming class to the message item. Outgoing messages are the messages with the same sender's id as the logged-in user. The rest should be easy to understand.

Now sending messages should work as expected.

If you reload the browser, however, you should see that your messages are gone. This is expected since we don't load previous messages on page load. This means the messages you send via CometChat are stored for you. So you don’t have to handle message persistence by yourself — how cool is CometChat?

Loading previous messages

Go to ChatMain.vue, and add the following method:

We should call this method every time we switch to a new contact. So let's call it from a watcher, like this:

Note how we're using immediate: true so it gets invoked when the component is mounted. We need this to load messages for the first contact that gets selected automatically when contacts are loaded.

Listening for new messages

Now what would happen if you logged in with another user from another browser and tried to send a message?

If you tried that, you wouldn't see the message until you reload the browser. This means we need to listen for new messages in realtime.

We can achieve that very easily with CometChat by adding this code to the mounted hook function in ChatMain.vue:

So we're listening for new messages using CometChat message listener, and we're showing messages only from the contact that's currently selected.

Showing current user statuses in realtime

You might already have noticed that the current statuses of the contacts aren’t updated in realtime — you have to reload the browser to see the current statuses.

Like with listening to messages, we can listen to user statuses using CometChat.addUserListener. Let's add this to the created hook function in Chat.vue.

So each time a user gets online or offline, we're notified in onUserOnline or onUserOffline callbacks with the user's data. We use the user's id to update the status of the matching user from the contacts list.

Showing the currently selected contact name

If you take a look at the header in the chat's main area, you'll see the text "Active Contact Name" regardless of the currently selected contact. Instead, we should display the name of that contact.

This can be easily done by replacing "Active Contact Name" with {{ activeContact.name }} in ChatMain.vue.

Fixing the browser's console error

If you check your browser now, you would see an error telling you that activeContact is null in ChatMain.vue. That's expected since we display the chat page before the contacts are loaded.

We can fix this quickly by including activeContact to the root element's check in the chat page.

Implementing logging out

Our last step in this tutorial is to implement the logout method in Navbar.vue.

So logging out is as simple as calling CometChat.logout(), and then redirecting to the login page.

Conclusion

It took time to build this app, but it's mostly because we made it look like a real-world app. CometChat was the easy part as you can see.

So we can conclude the flow of this chat app (or any chat app) like this: keep track of the logged-in user and his or her contacts, specify the contact when sending a message, load any previous messages between participants, and listen for new messages in realtime.

Thanks for reading! By the way, I’m writing a book on how to build a complete single-page application from scratch using Vue. Check out the book’s landing page if you’re interested in learning more about what the book will cover.

Taha Shashtari

CometChat

Taha is a full stack web developer. He’s been building apps with Vue since its early days. He’s written a number of popular Vue articles, and he’s writing a book on how to build an SPA with Vue.

Try out CometChat in action

Experience CometChat's messaging with this interactive demo built with CometChat's UI kits and SDKs.