How to Build a Group Chat App with Vue.js
Tags: vue, chat, pubnub
Published: 2019-06-10 06:04:25

---
canonical_url: https://www.pubnub.com/blog/vuejs-group-chat-app-tutorial/
---

Chat is everywhere and has ascended to one of the most important communication mediums across our daily lives. The number of [chat app](https://www.pubnub.com/developers/chat-resource-center/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q2-DevTo-June-09) use cases is vast and continues to grow. And with today's technological advancements, we expect our messages to send and receive in realtime, instantaneously. It's not a nice-to-have, it's a requirement. 

There are [thousands of ways to build a chat app,](https://www.pubnub.com/blog/building-chat-the-current-landscape/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q2-DevTo-June-09) and there are many things to consider: infrastructure, scalability, reliability, and security to name a few. With a vast amount of services, frameworks, and technologies to choose from, making that decision can be overwhelming!

In this tutorial, we're going to build a realtime group chat app in Vue.js. We'll power our app using [PubNub](https://www.pubnub.com/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q2-DevTo-June-09), which takes care of the heavy work for us; all we have to worry about is developing the app, not the underlying infrastructure. 

<a style="outline-width: 0px !important; user-select: auto !important;" href="https://ocastroa.github.io/Vue_Dist/dist/" target="_blank" rel="noopener noreferrer"><img class="size-medium wp-image-41643 aligncenter" src="https://www.pubnub.com/wp-content/uploads/2019/05/vue-group-chat-app.png" alt="Vue Group Chat App" width="250" height="350" /></a>

### Tutorial Overview

Our application will send messages to all connected users in the group chat using **publish**, and receive messages using **subscribe**. Our messages will be stored using **history**, so users can see past or missed messages. To do all this, we will use the [PubNub Vue.js SDK](https://www.pubnub.com/docs/vue-javascript/pubnub-javascript-sdk/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q2-DevTo-June-09). This tutorial is broken into two sections: **Publish/Subscribe** (Pub/Sub) and **History**. 

You can check out the complete project in the [GitHub repository](https://github.com/ocastroa/Vue_Chat.git).


## Setting up Pub/Sub Messaging

Before we start working on the app, sign up for a [free PubNub account](https://dashboard.pubnub.com/signup/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q2-DevTo-June-09). You can get your unique pub/sub keys in the [Admin Dashboard](https://admin.pubnub.com/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q2-DevTo-June-09).

Next, we need to install two dependencies: **vuex** and **pubnub-vue**. We can use **NPM** to do so.

*   _npm install vuex --save_
*   _npm install pubnub-vue --save_

Since the PubNub Vue.js SDK is a wrapper of the [PubNub JavaScript SDK](https://www.pubnub.com/docs/web-javascript/pubnub-javascript-sdk/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q2-DevTo-June-09), it offers all the same features. A few extra features, such as **Trigger Events**, are added to simplify the integration with Vue. One trigger event we'll be using is **$pnGetMessage**. We use **$pnGetMessage** with a reactive property so messages display as soon as they are received. To subscribe to a channel we use **$pnSubscribe** and to publish to a channel we use **$pnPublish**.

### Initialize the PubNub Client API

In the **main.js** file, create a unique UUID for each user and replace the pub/sub keys with your keys. We include two plugins: **PubNubVue** and **Vuex.**

```javascript
import Vue from 'vue'
import App from './App'
import store from './store';
import PubNubVue from 'pubnub-vue'

Vue.config.productionTip = false;

const publish_Key = 'YOUR_PUBLISH_KEY_HERE';
const subscribe_Key = 'YOUR_SUBSCRIBE_KEY_HERE';

// Make a unique uuid for each client
const myUuid = fourCharID();
const me = {
  uuid: myUuid,
};

try{
  if(!publish_Key || !subscribe_Key){
    throw 'PubNub Keys are missing.';
  }
}
catch(err){
  console.log(err);
}

Vue.use(PubNubVue, {
  subscribeKey: subscribe_Key,
  publishKey: publish_Key,
  ssl: true
}, store);

/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  components: { App },
  template: '<App/>',
  created
})
```

Next, we generate a random 4 character UUID for the user by making a call to the function **fourCharID**.

```javascript
function fourCharID() {
  const maxLength = 4;
  const possible = 'abcdef0123456789';
  let text = '';

  for (let i = 0; i < maxLength; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }

  return text;
}
```

It's recommended to use a **standard 128-bit UUID** in production apps, but the UUID can be a plain string as well, as is the case for this app. The constant ‘_me_’ holds the UUID. To commit the constant to the **[Vuex](https://vuex.vuejs.org) store**, we add the function **created**.

```javascript
function created(){
  this.$store.commit('setMe', {me});
}
```

This function will execute when the Vue instance is created.

### Setting Up the Vuex Store

For the **store.js** file, we set up the **centralized store** that holds and manages the application state. The file will contain a [global state object](https://vuex.vuejs.org/guide/state.html) along with a collection of [mutation](https://vuex.vuejs.org/guide/mutations.html) and [getter](https://vuex.vuejs.org/guide/getters.html) functions. Since outside components cannot access the state directly, we commit a **mutation** every time we need to update the state.

```javascript
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const state = {
  me: {},
};

const mutations = {
  setMe(state, {me}) {
    state.me = me;
  },
}

const getters = {
  getMyUuid: (state) => state.me.uuid,
};

export default new Vuex.Store({
  state,
  getters,
  mutations,
});
```

The _getMyUuid_ getter is referenced in three of the components and is a **global getter** for the UUID.

### ChatContainer Component

The ChatContainer component is the highest parent node of the UI. The markup includes **custom HTML tags** for the children components of ChatContainer, as well as **Vue specific markup** to reactively render data.

```html
<template>
  <div class="chat-container">
    <div class="heading">
      <h1>{{title + '- User: ' + uuid }}</h1>
    </div>
    <div class="body">
      <div class="table">
        <chat-log></chat-log>
        <message-input></message-input>
      </div>
    </div>
  </div>
</template>
```

The **h1** curly braces bind two JavaScript variables, _title_ and _uuid_, reactively evaluates the expressions and displays the text output as the text content in the tag. The variable _title_ gets its value from the function **data**.

```javascript
data() {
  return {
    title: 'PubNub & Vue Global Chat',
  };
},
```

Before we discuss the variable _uuid_, let’s discuss the imports and the two properties above **data**.

```javascript
import ChatLog from '@/components/ChatLog';
import MessageInput from '@/components/MessageInput';
import {mapGetters} from 'vuex';
```

Since we are using the **chat-log** and **message-input** tags in the markup, we need to import the **ChatLog** and **MessageInput** components so the tags are rendered properly. We also import **mapGetters** to get the UUID from the store.

```javascript
export default {
  name: 'chat-container',
  components: {
     ChatLog,
     MessageInput,
  },
```

The **name property** is ‘_chat-container_’ and it coincides with the kebab-case HTML tag name in the markup. We include the **components** **property** to tell Vue which components are being referenced in the markup. Now, getting back to the variable _uuid_ in the **h1** curly brace, we need to set up the **computed property** which uses **mapGetters** to map the getter ‘_getMyUUID_’ to _uuid_.

```javascript
computed: {
 ...mapGetters({
     uuid: 'getMyUuid',
  ),
}, 
```

Since we are mapping the getter to a different name (_uuid)_, we use an object. Finally, we include the function **mounted** to subscribe to the channel '_vueChat'._

```javascript
mounted() {
  this.$pnSubscribe({
      channels: ['vueChat'],
  });
},  
```

We subscribe, using **$pnSubscribe**, to the channel once the Vue instance is mounted to the **DOM**.

### MessageInput Component

We break this component into 4 parts:

1.  First, we check that the message body is not empty.
2.  Then, we get the user UUID from the store and assigned it to the variable _userUUID_.
3.  We publish the message, along with _userUUID_, to the channel '_vueChat'_.
4.  Finally, we reset the text input.

Here is the template for the component.

```html
<template>
  <div class="message-input">
    <textarea
      ref="messageInput"
      placeholder="message..."
      maxlength="20000"
      @keydown.enter="submitMessage"
    ></textarea>
  </div>
</template>
```

When the user types in the message body and presses enter, the function **submitMessage** is called to check that the message body is not empty. We include this function inside of **methods**. (Note: The rest of the code for **MessageInput Component** will be inside of **submitMessage**).

```javascript
methods: {
  submitMessage(event) {
    if (!event.shiftKey) {
       event.preventDefault();
     } else {
        return;
     }

     // If the message body is empty, do not submit
     if (event.target.value.length === 0) {
       return;
     }
```


We access the getter, _getMyUUID_, and assign it to the variable _userUUID_.

```javascript
const userUUID = this.$store.getters.getMyUuid;
```

If the user presses enter and the message body is not empty, we publish to '_vueChat_ ' the text and the user’s UUID.

```javascript
this.$pnPublish({ 
  channel: 'vueChat', 
  message: {
    text: event.target.value,
    uuid: userUUID,
    },
  })
```

We reset the text input once the user presses enter.

```javascript
event.target.value = '';
``` 

### MessageBubble Component

The chat log will display the messages sent and received in a message bubble. We'll get to the chat log component in a bit, but for now, let’s focus on the message bubble component. When you send a message, the message bubble is shown on the right side of the chat log without displaying your UUID. The messages received from other users are shown on the left side of the chat log with the users UUID shown above the bubble. This follows the design logic of many group chat apps.

```html 
<template>
  <div
    class="message-bubble"
    :class="me"
  >
    <span
      class="from"
      :class="me"
    >uuid</span>
    <br :class="me">
    <span
      class="message-text"
    >text </span>
  </div>
</template>
```

The class ‘_me_’ is bound to a class, such as  ‘_message-bubble_’ or ‘_from_’, only if you send the message. When ‘_me_’ is bound to a class, the positioning and styling of the message bubble will change, as mentioned above. A computed property is used to check if the user is yourself or someone else.

```javascript
export default {
  name: 'message-bubble',
  props: ['uuid','text'],
  computed: {
    me() {
      let result = false;
      // Check if the client uuid of the message received is your client uuid
      if (this.$store.getters.getMyUuid === this.uuid) {
        result = true;
      }

      // Render the message bubble on the right side if it is from this client
      return result ? 'me' : '';
    },
  },
  data() {
    return {};
  },
};
```

Besides the computed property, another important part in the script are the **prop attributes** that are registered to the MessageBubble component. The two values in the prop, ‘_uuid_’ and ‘_text_’, will be passed to MessgeBubble's parent component, ChatLog.

### ChatLog Component

The chat log displays the messages once it’s received in the channel. Before we work on the template, let’s do the script section first.

```javascript
import MessageBubble from '@/components/MessageBubble';
import {mapGetters} from 'vuex';
function scrollBottom() {
  this.$el.scrollTo(0, this.$el.scrollHeight);
}
```

Since we'll be using the _message-bubble_ tag in the markup, we need to import the MessageBubble component in the script so the tags are rendered properly. The **scrollBottom** function auto scrolls the chat log to the bottom whenever a message is received. This function is called in the [watch](https://vuejs.org/v2/api/#watch) property.

```javascript
watch: {
  vueChatMsg: function(){
    this.$nextTick(scrollBottom);
  }
},
```

We use **.$nextTick** to make sure the **scrollBottom** function is called only after the DOM has been updated. Next, let’s add the name property, components and the data function.

```javascript
name: 'chat-log',
components: {MessageBubble},
data() {
  return {
    vueChatMsg: this.$pnGetMessage('vueChat'),
  }
},
```

The data function returns _vueChatMsg,_ which contains the new message payload from the channel. As mentioned before, since we are using **$pnGetMessage**, the messages will display as soon as they are received. We include the channel name as a parameter. The _vueChatMsg_ property holds an array of objects where every object of the array is the [Subscribe Message Response](https://www.pubnub.com/docs/vue-javascript/api-reference-publish-and-subscribe#subscribe/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q2-DevTo-June-09). For every message published, a new message response is added to the array. The objects in the message response includes information such as the channel name, the publisher, the message payload, the subscriber, and so on. We only want the message payload which includes the ‘_uuid_’ and ‘_text_’. We will implement this logic in the template.

```html
<template>
  <div
    class="chat-log"
    ref="chatLogContainer"
  >
  <message-bubble
    v-for="msg in vueChatMsg" 
    v-bind:key="msg.id"
    v-bind:uuid="msg.message.uuid"
    v-bind:text="msg.message.text"
  ></message-bubble>
  </div>
</template>
```

We use **v-for** to iterate _vueChatMsg_ with ‘_msg.id_’ as the unique key. We use **v-bind** to bind the two prop values, ‘_uuid_’ and ‘_text_’. Remember that we declared the prop in the child component MessageBubble. So for every iteration of the for loop, we only iterate the message payload and bind ‘_msg.message.uuid_’ and ‘_msg.message.text_’ to its corresponding prop attribute. 

Let’s quickly summarize the above. Every time a message response is received, it is added as a new element to the array of objects, _vueChatMsg_, which is returned by the data function. When this happens, inside the _message-bubble_ tag we iterate, using v-for, the new element in the array. Since we only want the message payload, v-for only checks for ‘_message_’ which contains the payload. The payload values ‘_uuid_’ and ‘_text_’ are bind to its appropriate props. Both values are then sent back to the child component, MessageBubble. 

That’s all for the Pub/Sub section of this tutorial. Make sure that the code is correct and that you have installed the appropriate plugins. Get the **CSS** section of the four components from the [repo](https://github.com/ocastroa/Vue_Chat.git). Run your program by typing '**npm install**’ and ‘**npm run dev**’ in the terminal and your program should start on a localhost port. Type a message in the message input and you should see a blueish bubble on the right side of the chat log. Open another tab, or preferably window, and copy and paste the URL. Now type a new message in the message input and again, you should see the blueish bubble on the right side of the chat log. But now, you should also see the new message on the other chat log. This new message should be a greyish bubble on the left side of the chat log. Play around with both chat windows and see the messages appear in realtime on both screens.

## Storing Chat Messages with the History API

While everything is set up and ready to use, there is one problem. If you reload the page, you will notice that all the messages disappear. This occurs because the [Storage & Playback](https://www.pubnub.com/docs/vue-javascript/api-reference-storage-and-playback/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q2-DevTo-June-09) feature is not turned on. To turn it on, go to the [PubNub Admin Dashboard](https://admin.pubnub.com/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q2-DevTo-June-09) and click on your application. Click on **Keyset** and scroll down to **Application add-ons**. Keep scrolling down until you get to **Storage & Playback** and toggle the switch to **on**. Keep the default values the same. 

![Storage and Playback Toggle Switch](https://www.pubnub.com/wp-content/uploads/2019/05/storage-and-playback-toggle.png)

Now that it’s on, messages will persist in storage and can be retrieved later on. Messages can also be deleted from history to meet [GDPR compliance.](/products/security/gdpr/) If you cloned the repo, then reload the page for the chat app and the messages should appear in oldest to newest order. If you didn’t clone the repo, the messages won’t appear since the **history function**, which fetches historical messages of a channel, has not been added to the code. Either way, in the next section, we will implement the history function so messages can be stored and retrieved.

## Setting up History

Fetching the historical messages of our channel is not difficult to do. We need to make small modifications to three files: **store.js**, **ChatContainer.vue**, and **ChatLog.vue**. Let’s start with **store.js**.

### Modify the Vuex State

In the **state**, we need to add a new property, **history**, with an empty array as the value.

```javascript
const state = {
  ...
  history: [],
};
```

In **mutations**, we add a new mutation, **addHistory**, with _state_ as the first argument and _history_ as the second argument.

```javascript
const mutations = {
  ...
  addHistory(state, {history}){
    history.forEach(element => {
      state.history.push(element.entry);
    });
  },
}
```

We iterate the array _history_ that contains the historical messages retrieved from the channel. Each element in the array contains two keys, _timetoken_ and _entry_. We only want _entry_ since it contains the text the user entered and their UUID. This is why in each iteration we push **element.entry** to the **history** array we added in **state**. We will only add one line to **getters**.

```javascript
const getters = {
  ...
  getHistoryMsgs: (state) => state.history,
};
```

### Modify ChatContainer

Since we need to use the history function, import **PubNubVue**.

```javascript
import PubNubVue from 'pubnub-vue';
```

Below the imports, we add a new function, **fetchHistory**, that will fetch 6 messages from the channel. You can change the number of messages to fetch with 100 being the max number.

```javascript
function fetchHistory(store){
  PubNubVue.getInstance().history(
      {
        channel: 'vueChat',
        count: 6, // how many items to fetch
        stringifiedTimeToken: true, // false is the default
      },
      function(status, response) {
        const msgs = response.messages;
        // Iterate msgs array and save each element to history
        msgs.forEach(elem => {
          store.commit('addHistory', {history: [elem]});
        })
      }
   )   
}
```

To commit the history payload, save _response.messages_ to the constant variable ‘_msgs_’. The constant contains an array of objects where each object contains two keys (_timetoken_ and _entry)_. We don’t want to commit the whole array to the **Vuex Store**, rather, we want to iterate the array and commit each element. This will make it easier to fetch the necessary keys in the **addHistory** function. The last modification to include is in **mounted** which makes the call to **fetchHistory**.

```javascript
mounted() {
  ...
  this.$nextTick(fetchHistory(this.$store));
},
```

We pass **this.$store** as a parameter so we can commit the changes to the store.

### Modify ChatLog

This is the last file we need to update. We need to make changes to the template and the script. Let’s start with the script. We need to import **mapGetters** since we will be using it in the computed property.

```javascript
import {mapGetters} from 'vuex';
```

In the computed property, we map the getter ‘**getHistoryMsgs**’ to **history**.

```javascript
computed: {
  ...mapGetters({
    history: 'getHistoryMsgs',
  }),
}, 
``` 

In the template, we add another _message-bubble_ tag.

```html
<template>
  ...
   <message-bubble
    v-for="historyMsg in history" 
    v-bind:key="historyMsg.id"
    v-bind:uuid="historyMsg.uuid"
    v-bind:text="historyMsg.text"
  ></message-bubble>
  ...
</template>
```

This looks very similar to what we did earlier. We use **v-for** to iterate history. At every iteration, we retrieve the ‘_uuid_’ and ‘_text_’ from the array and bind it to its appropriate prop attributes. The messages will show in the chat log as message bubbles. That is all we need to do for history. Run the program again and you should see the last six messages from history in the chat log. 

<a style="outline-width: 0px !important; user-select: auto !important;" href="https://ocastroa.github.io/Vue_Dist/dist/" target="_blank" rel="noopener noreferrer"><img class="size-medium wp-image-41643 aligncenter" src="https://www.pubnub.com/wp-content/uploads/2019/05/history-messages-group-chat-app.png" alt="Vue Group Chat App" width="250" height="350" /></a>

There are two things to take note of. The first thing is that the messages will persist in storage for 1 day only. After 1 day, the messages are removed from storage. You can change the time period a message is stored by changing the **retention time**, which is located in the **Storage & Playback add-on**. For the purpose of this tutorial, we leave the default value of 1 day. 

The second thing to note is that the history messages will show on the left side of the chat log, even if the messages are from you. This is because we generate a random 4 character UUID every time the app instantiates. So when you reload the page, a new UUID is assigned to you and the previous messages you sent before the reload will now be seen as messages sent from another user. This is fine for this tutorial, but for real production, each user should have a unique UUID that is persistent. For a persistent UUID, the history messages that are sent by you will show on the right side of the chat log.

## What's Next?

Now that you've got your basic messaging functionality implemented, it's time to add more features! Head over to our [Chat Resource Center](https://www.pubnub.com/developers/chat-resource-center/?utm_source=Syndication&utm_medium=DevTo&utm_campaign=SYN-CY19-Q2-DevTo-June-09) to explore new tutorials, best practices, and design patterns for taking your chat app to the next level.