Building a Slack Clone Using Vue.js – Part 1

Published April 16, 2020 by Luke Oliff

Building a Slack-like Vue.js Chat Application

Have you ever wanted to create a chat application, but get stuck on features to add, or just how to make it generally? In this post, you get to build a clone of everyone’s favourite chat software, Slack. Using Vue.js, everyone’s favourite framework. And, Vonage Conversation API, everyone’s favourite conversation service.

This post is part 1 of a multi-part tutorial series that’s going to go from an empty directory to a real-world application featuring many of Slacks genre-defining features.

Here are some of the things you’ll learn in this post:

If you’re interested in the demo app complete, skipping the guide completely, please check out the GitHub repo for my Vue.js Slack clone so far.


Node & NPM

To get started, you’re going to need Node and NPM installed. This guide uses Node 8 and NPM 6. Check they’re installed and up-to-date.

Both Node and NPM need to be installed and at the correct version. Go to, download and install the correct version if you don’t have it.


To set up your application, you’ll need to install our CLI. Install it using NPM in the terminal.

You can check you have the correct version with this command. At the time of writing, I was using version 0.4.9-beta-3.

To follow along with the steps in this article, remember to sign up for a free Vonage account and configure the CLI with the API key and secret found on your dashboard.

Express.js CLI

Install Express Generator. You will use this library to generate a basic Express.js server.

You can check you have the correct version with this command. At the time of writing, I was using version 4.16.1.

Vue.js CLI

Install the Vue CLI. You will use this library to generate a basic Vue.js client application.

You can check you have the correct version with this command. At the time of writing, I was using version 4.1.2 of @vue/cli.

Starting From Scratch

This series is going to take you from a blank directory right through to a real-world chat application using Express.js as a server.

Create a Project Folder

First thing first, create a directory for your work.

And, change into the new directory.

Generate an Express.js Server

Next, create a basic server using the Express.js generator. The thing I love about this CLI is that it configures the server executable and application independently of each other. Meaning, it takes the philosophy of the extremely lightweight and cool Express Hello World. It splits it into the equally cool executable file for configuring the server and the environment bin/www, and the application itself app.js.

Because the application is predominately an API, it’s better to skip installing anything used for handling template files. For this, use the --no-view option.

If you plan on using git as your version-control system, you should consider using --git to generate the correct .gitignore file.

Because you’re already in the project directory, specify the --force option and use . as the directory. Then, the tool will generate the application in the current directory without issue.

Then, install dependencies.

Run the Express.js Server Locally

Once the server has been created and the dependencies installed, you can go ahead and start it to make sure everything is working as expected.

You can check it’s working at the default URL, localhost:3000.

Screenshot of a basic Express.js server running

Routes and Controllers

The generated application includes the necessary routing. Routing refers to determining how an application handles a request to a particular URL and method (GET, POST, etc.). Controllers, on the other hand, are responsible for the flow of the application execution. The generated application doesn’t create and controllers and uses the routers to return a response.

Create a new controller directory.

Create a new controller in this directory named server.js.

Open controllers/server.js and create the first method for the server.

This controller could later be responsible for providing the client with a condition, driven by various checks like if the chat service is up and running or whether it can connect to the data. The idea is that if any issues occur on the server, the client will receive the error, gracefully handle it, and inform the user what has happened.

To request this controller method, create a new route in the existing routes directory named server.js.

Open routes/server.js and add the code shown below.

This routes a path (/status) to a controller method (serverController.status). The route delivers the result of the controller method to the client as a response.

To add this route to the app, you need to edit app.js and make these changes.

Then you can go ahead and delete the routes/index.js and routes/users.js files.

Start the application again with npm start; then you can access the new route at localhost:3000/api/server/status.

Screenshot of a basic server status API endpoint

Creating a Client

Use the Vue CLI to create a new client application.

Generate a Vue.js Client

Run the create command with the Vue CLI. This tool generates a simple Vue application to base our chat client off. It prompts with some options, and you can select the defaults.

The client is generated in the client directory as specified in the command. It also runs npm install automatically.

Now, change into the client directory.

To run the client, use this command. Notice, it is different from how you run the server.

Then you can access your client at localhost:8080. You’ll notice it has a different port by default and in the development environment this helps us as you’ll find out next as we run the server and client concurrently.

Screenshot of a basic Vue.js client running

Hot Reloading the Express.js Server Files

Usually, in the development process, most people like the application to automatically reload the files as they edit them. To do this, we’ll set up the server to use nodemon to serve the files.

Install Nodemon

If you’re still in the client directory from earlier, you can change back to the projects main directory by going up a level with this command, .. denoting a parent directory.

Now, install Nodemon as a development dependency. Install a development dependency by adding --save-dev as an option of the command.

Once installed, you can edit the package.json file and modify the start script as shown here.

When you run the application with npm run dev:server, it will use Nodemon. Nodemon watches the application files and restarts the service automatically when any files change.

Note: This also includes file metadata like permissions and modified date.

Run the Server and Client Concurrently

As we progress in this guide, you’re going to need to run both the client and Express.js concurrently. There is a Concurrently package for that, which makes it very easy to lean separate applications on each other.

Install Concurrently

Install Concurrently, also as a development dependency.

Start Both Dev Environments

Modify the package.json file for the server, as shown here. In the last section, we added a dev:server script which ran the server using Nodemon. Now, we’re adding a dev:client script at the root level of the project to run the client from here too.

Now, add this line to combine the two using Concurrently. You’ll notice the option --kill-others-on-fail which means that concurrently will stop all services if a hard error is detected. Without this, if Node or Webpack (which serves the client) encountered an error, you would need to restart Concurrently to get both client and server running again.

When you run the application with npm run dev, it will start both server and client together at localhost:3000 and localhost:8080 respectfully.

Screenshot of a Express.js and Vue.js running concurrently

Proxy API Requests to the Express.js Server

To make requests in the development environment to the server from the client, you’ll set up a proxy. You can configure Vue.js to proxy any requests beginning with a particular route.

Configure the Proxy

To do this, create a new file inside the client directory named vue.config.js. So change into the client directory.

Create an empty config file.

Paste in the following code.

This code tells Vue.js that when running devServer that any routes matching /api should proxy to http://localhost:3000. This is the URL for the server when you run the dev script, or the dev:server script directly.

Create an API Consumer Service

To make requests from Vue.js to our server from the client, install Axios, which is a Promise based HTTP client to use in browser-side code.

Now, you have Axios installed and you can proxy requests between the server and client, it’s time to make those requests. In the client’s src/ directory, make a new directory named services to contain all of the API service files.

Create an abstract API service, which will set the path for subsequent API services. Remember, in the development environment, /api is going to proxy to the server.

Use the following code to create an abstract API service that returns an Axios instance.

You’ve already created a server/status endpoint in the server, which when the server was running you could access from localhost:3000/api/server/status.

To consume this endpoint from the client application, create a file for the service.

And, add this code to create a fetchStatus method on the new Server service.

Request Server Status in the Client

Now that you’ve created a service to make requests to the server, import the service into your App.vue component.

Open App.vue and add the lines as shown here.

Here, it reuses the HelloWorld component to display the status of the request to the user.

Note: Remember, you’re probably still in the client directory at this step. Starting (or restarting) the development environment again using npm run dev needs to run in the server directory (cd .. to go from client to server).

Once it’s running, you can access the client at localhost:8080. If you’re quick enough, you can see the “Connecting…” message.

Screenshot of the Vue.js client connecting to the Express.js server

Loading Screens with Tailwind and FontAwesome

While connecting to the server in the last section, you’ll have reused the HelloWorld component. Now, using the Tailwind CSS low-level CSS framework and FontAwesome, create a loading screen for the client.

If you’d like to practice this in isolation of this app, I wrote about Using Tailwind CSS with Vue.js in a separate guide just for you.

Install Tailwind CSS

To use Tailwind CSS in the client, we have to install it as a dependency and configure the client to use it.

Note: This install is for the client, not the server. So ensure you’re in the client directory.

Configure Vue.js Client for Tailwind CSS

When the client app builds, it looks for a postcss.config.js file that is a config file that Vue.js uses to know how to process CSS. The Tailwind CSS install says you’ll want to add it as a plugin in your build chain.

The demo app generated by Vue doesn’t create a postcss.config.js file. Do that now.

And, configure it using this code.

Add Tailwind as a CSS Asset

The demo app also doesn’t create any CSS assets. Instead, it uses CSS inside Vue.js components, which many guides show. So, to include tailwind, create a basic CSS file inside the assets directory using these commands or your editor.

Use this code to include the Tailwind CSS base, components, and utilities inside your CSS build. Copy and paste it into your new index.css file.

Include Tailwind CSS

Now edit your main.js file to import index.css to the client.

Screenshot of the Vue.js client styles after Tailwind CSS preflight enabled

Note: Tailwind CSS uses preflight (built on top of normalize.css) to reset all the styling on different browsers to the same place. You’ll notice it’s broken some default styling. Don’t worry; you’ll replace this altogether soon.

Install FontAwesome

Creating a loading spinner will be done with a font awesome notched circle. Install it to the client with this command.

Include FontAwesome

Edit main.js again and add this code.

Create the Loading Screen

To create a new Vue.js component to use as a loading screen, add a new component file with this command or your editor.

Now using this code, add the spinner to a fullscreen translucent overlay.

And, add the loading screen by editing App.vue and replacing the reuse of HelloWorld.vue with the new component.

Screenshot of the Vue.js client loading screen with spinner

Note: To test this, you can modify the response status in the server’s controllers/server.js directory to return something other than ok.

Handle Server Errors in the Client

It is time to add error handling to the client.

Catch Request Errors

Edit App.vue and add the following code.

Now, if there is an error back from the server, it will be caught by the client and added to the component data.

Create an Error Component

To display an error, create an empty Error.vue component using this command or your editor.

Add this code, which also uses FontAwesome icons (and layers) to produce an appropriate graphic.

Display a Server Error in the Client

Once again editing App.vue, add the code as shown here. Remove the image at the same time.

Now, the client displays errors sent by the server.

Screenshot of the Vue.js client catching a server error

Note: To see this working, you can modify your server’s application controllers/server.js, replacing res.json with res.sendStatus(500) to provide the client with a 500 error code.

Use Dotenv Environment Files

You don’t want to hardcode keys and credentials into your server, but especially not in your client.

Install Dotenv

Install dotenv so you can set environment variables and read them in your application.

Note: You might be in the client directory at this step. Use cd .. to go from client to server.

Create an Environment File

Create an empty environment file for the server using this command or your editor.

Configure the Environment

Now, edit .env and add this example configuration to the file. The token and ID are not real.

Note: .env files are ignored by git due to the generated .gitignore file adding .env by default. Committing your .env file is about as safe as hardcoding your credentials. It also highlights that these credentials are for this environment, running it locally. If you were to deploy this, expect to manage the environment on the server in a different way. Heroku, for example, provides you with a control panel for configuring the environment.

Load the Environment

Now, edit the server top file to include the environment when the application starts. Edit bin/www (it has no file extension) as shown here.

Pass Server Environment Values to the Client

The first environment variable to share with the client is VONAGE_DEFAULT_CONVERSATION_ID, the default “room” ID for the chat! You’ll come back and edit the value of the environment variable later.

Edit controllers/server.js and add the code shown here.

User Endpoints for Client Authentication

In later parts of this series, an identity provider will manage the user data sent by the server. In the meantime, fake this information too, and come back to edit it when you have it.

Create a User Endpoint

Create a user endpoint by first creating a user.js controller using your editor or this command.

Giving it this code.

Now, create a route to access the user controller endpoints using your editor or this command.

And, give it this code.

Lastly, edit your app.js file and add the new route as shown here.

Start the application again with npm start; then you can access the new route at localhost:3000/api/user/session.

Screenshot of a user session API endpoint

Connect to Vonage Conversation API

In this section, what follows are the usual steps if you’ve read one of my client-side tutorials before. If you haven’t, these are simple commands to create our Vonage conversation for users to join.

Set Up With Our CLI

To connect to the conversations API as a user, you first need to create an application, conversation, and user.

Create an Application

Create an application with RTC (real-time communication) capabilities. The event URL receives a live log of events happening on the service, like users joining/leaving, sending messages. It’s an example URL for the moment, but you’ll be able to capture and react to events in later parts of our series.

Create a Conversation

Secondly, create a conversation, which acts like a chatroom. Or, a container for messages and events.

Create Your User

Now, create a user for yourself.

Note: In this demo, you won’t chat between two users. Other guides show you how to create conversations between multiple users. This guide focusses on styling your message UI in a simple, yet appealing, way.

Add the User to a Conversation

Next, add your new user to the conversation. A user can be a member of an application, but they still need to join the conversation.

Generate a User Token

Lastly, generate your new user a token. This token represents the user when accessing the application. This access token identifies them, so anyone using it will be assumed to be the correct user.

In practice, you’ll configure the application with this token. In production, these should be guarded, kept secret and very carefully exposed to the client application, if at all.

The token is only usable for 24 hours. After that, you will need to re-run this nexmo jwt:generate command again to grant access to your client user again.

Store the Credentials in the Environment

Now, edit .env and add the credentials you’ve now generated.

Create a Service for the User Session

Create a User.js service to consume the user session endpoint from the client application.

Create the file using this command or your editor.

And, add this code to create a fetchSession method on the new User service.

Connect the Client to the Conversations API

To connect the client to the Conversations API, you need to install the latest version of the nexmo-client.

Create a new Vonage.vue component using your editor or the command below, which will have the responsibility of connecting to the Conversation API using the nexmo-client library.

Similar to the App.vue component, the Vonage.vue component requests user-session information from the server, using the Loading.vue and Error.vue components in the same way, too.

Now, replace the use of the HelloWorld.vue with the new Vonage.vue component inside App.vue by making these changes.

Now, after your “Connecting…” loading screen, you’ll see a “Logging you in…” loading screen before it finally loads the HelloWorld.vue component.

Screenshot of client logging into the Conversation API

Note: You’ll only reach the Hello World if your application has successfully connected to the server, got an “OK” status, requested the user session and then used the user’s token to connect to the Conversation API using the nexmo-client library.

Create the Chat Components

Now you’re connected to the Conversation API; you can start creating your messaging UI. First, start with the basic structure of your application, the Chat Window.

Chat Window

For this, create the components ChatWindow.vue, ChatWindowHeader.vue, ChatWindowEvents.vue, and ChatWindowFooter.vue using the command or your editor.

Editing ChatWindow.vue, give it the following code.

The ChatWindow.vue component is responsible for structuring the chat layout. Header at the top, messages in the middle, and the footer at the bottom. It passes the channel name, prefixed with a hash, as the channelName prop to the header. It also passes the conversation, user and members through to the events component. Then, it passes the conversation to the footer.

Next, edit ChatWindowHeader.vue and give it this code.

The ChatWindowHeader.vue component, for now, just displays the channel name.

Now, edit ChatWindowEvents.vue and give it this code.

The ChatWindowEvents.vue component is responsible for listing all the events in the conversation. It does this top to bottom, older events being at the top of the window. Scroll down to see the most recent messages. It loads a total of 40 messages. Later in the series, you’ll see how to load older messages.

Finally, edit ChatWindowFooter.vue and give it this code.

With your components created, edit Vonage.vue and replace HelloWorld.vue with your new ChatWindow.vue component.

Lots to copy and paste here. Once running, see what it looks like.

Screenshot of the chat client working

Notice the margin, leftover from the demo app! Lastly, remove this styling by editing src/App.vue like so.

While you’re at it, delete HelloWorld.vue. Finally.

Screenshot of the chat client working beautifully

Working Chat Achieved!

Part 1, complete! You’ve built a chat client that is starting to resemble Slack. Here’s a list of what you’ve done so far:

  • Made an Express.js app to use as an API
  • Made a Vue.js app to use as a client
  • Created API endpoints in Express.js
  • Consumed API endpoints in Vue.js
  • Added hot reloading of Express.js files
  • Added concurrently to Express.js and Vue.js with one command
  • Proxied API requests from Vue.js to Express.js
  • Styled Vue.js with Tailwind CSS
  • Animated icons with FontAwesome
  • Made a full-screen loading component
  • Connected to the Vonage Conversation API
  • Created a Messaging UI

If you’re interested in the demo app complete, please check out the GitHub repo for my Vue.js Slack clone so far.

Stay tuned for part 2, where we tackle the following user experience must-haves.

  • Infinite scrolling history
  • Sticky scroll positions when scrolling history
  • Ping to bottom on sending messages
  • Unread message notifications
  • Mark-as-read button
  • Number of channel members
  • Message deletion
  • User typing events notification (several people are typing)
  • Multi-line messages
  • Slack style Markdown

By the end of Part 2, you’ll have something that looks more like this!

Screenshot of the sneak peek of chat from Part 2

Further Reading

Here are some more articles you may find helpful in your journey to create a web-based chat app.

And don’t forget, if you have any questions, advice or ideas you’d like to share with the community, then please feel free to jump on our Community Slack workspace or pop a reply below 👇

Leave a Reply

Your email address will not be published.

Get the latest posts from Nexmo’s next-generation communications blog delivered to your inbox.

By signing up to our communications blog, you accept our privacy policy , which sets out how we use your data and the rights you have in respect of your data. You can opt out of receiving our updates by clicking the unsubscribe link in the email or by emailing us at [email protected].