The basics: send and receive messages

--

Last month, my friend Anthony working in a big, corporate company told me

“We love Slack. However, we don’t want our messages to be stored in a cloud. We would like to start building a self-hosted clone in the near future: how would you do it?”

Here’s my answer.

For the front-end, I had many choices. After taking a close look at some of them (basically, the most popular, namely React, Angular 2, Cycle and Vue), I’ve chosen Vue. Why?

  • It’s straightforward. It has a CLI that sets up a great development environment with hot-reload working out of the box.
  • It’s elegant. Once we take a look at the file structure later, we see how clearly it is organized. Way better than JSX `return` values or Angular 2 `template` strings with no code highlights. `.vue` files are neat and beautiful.
  • It’s reactive. 2-way data binding is just effective. No “magical” zones like in Angular 2, no DOM re-renders. Just neat and easy to understand.
  • It has awesome developer tools. Check out the Chrome extension and just say goodbye to pain.

Vue is a front-end component library started as a personal project by Evan You, a former Google and Meteor engineer. It’s open-source and it has a high-level support from Evan and the quickly-growing community. Evan is currently fund-raising on Patreon.

For the back-end, I’ve been facing a big bunch of solutions. I’ve chosen Kuzzle.io, here’s why.

  • It’s self-hostable and Open Source. I love the cloud, except when it is raining too much. And what recently happened with Parse was a hell of a storm for many of us. We’ll avoid embarrassing situations by hosting our own back-end.
  • It’s straightforward. I find that being able to have it working by just typing `docker-composer up` is quite a sunny solution. And yeah, I love the sun.
  • It’s complete. Data storage, search, realtime I/O and user management. What else do we need?
  • It has an awesome SDK. Many ready-to-use backend solutions provide front-end SDKs, but Kuzzle’s one is so offline-first... I love to see that it just works even when the connection goes up and down.

Kuzzle.io is a ready-to-use and extendable back-end application developed in Montpellier, France. It has recently reached the Beta release and it is getting growing consensus on the web development community.

Front-end environment setup

Prerequisites. To follow this How-to you should be familiar with the Node.js environment and npm, modern Javascript coding in general and the Docker ecosystem.

If you want to see everything running, you can check my repo at https://github.com/xbill82/slack-vuejs-kuzzleio . Just clone it and checkout the tag part-1.

Vue.js kick-start

First, we create a Vue project by using the official CLI.

$ npm install -g vue-cli
$ vue init webpack slack-clone

$ cd slack-clone
$ npm install
$ npm run dev

So, what’s happened here? The Vue CLI has initialized a new project for us with all the necessary files and directories. Then we npm installed the dependencies specified in the generated package.json. Lastly, we npm run our development server.

A “development server” is a small Express instance that uses Webpack as a middleware to watch our Vue files and compile them to browser-friendly javascript on-the-fly. Every time we change a chunk of code, it is transparently updated in the front-end without needing a page reload (this is called Hot Module Replacement and you are going to love it).

Now, if you go to http://localhost:8080/ you can see an auto-generated “Hello World” project.

Hands on Code!

Ok, let’s do it. Go to src/App.vue and take a look at the top-level tags:

<template>...</template><script>...</script><style>...</style>

Guess what goes in each of them?

  • template contains your markup;
  • script contains your interaction logic;
  • style contains your component-specific stylesheet.

This is how every Vue component looks like. App is simply the root component, which is instantiated and attached to the DOM in src/main.js (our entry point). This will be the only time the DOM is directly manipulated.

If you like discovering things on your own take a look of what’s inside each tag but, for now, you can just delete their content and leave them empty, we’ll overwrite everything once we’ll have created…

…Our First Component

The most basic thing we use instant messaging for is… Writing stuff! Our first component will be called src/components/InputBar.vue and guess what it will look like?

<template>
<div class="bottom-input">
<div class="input-box">
<input
type="text"
class="input-box-text"
placeholder="New message"/>
</div>
</div>
</template>

Easy. We inserted an input element into the template tag of the component. Let’s display it by inserting it into src/App.vue

<template>
<input-bar></input-bar>
</template>

<script>
import InputBar from './components/InputBar'
export default {
components: {
InputBar
}
}
</script>
<style></style>

What has happened here? We have told the template tag that we want an input-bar element. To enable Vue to find it, we have imported and declared it as a component into the script tag. The result? You should see an input field on your page.

Where are my messages?

Ok, this isn’t so groundbreaking. After all, a Slack that doesn’t display messages is a bit… pointless? We need to add some more bits here.

Create a new src/components/Message.vue file like the following:

<template>
<div class="message">
<span class="message-content">{{message.content}}</span>
</div>
</template>
<script>
export default {
props: ['message']
}
</script>

This component is quite like the previous one but with two slight differences:

  • The {{message.content}} statement in the template. This is data binding expression. It means “put here the content of the variable message.content”.
  • The props keyword in the script tag. Props are local variables that contain data that is passed from parent components. We’ll use them a little bit later but, basically, what we are saying here is “The message variable comes from parent”.

Then create a src/components/Messages.vue file like the following:

<template>
<div class="message-history">
<message v-for="message in messages"
:message="message">
</message>
</div>
</template>
<script>
import Message from './Message'
export default {
data () {
return {
messages: [
{content: 'Hey! Ho!'},
{content: 'Let\'s go!'}
]
}
},
components: {
Message
}
}
</script>

Yay! More new things.

  • The v-for loop directive in the template. It’s just a for loop and it goes like “display as many message components here as many elements you find in the messages array. Also, pass each element as the message prop to each component”.
  • The data method in the script tag. We use this to locally declare some temporary dummy messages to display in the list. We’ll replace this with something more interesting later…

Altogether now! Make src/App.vue look like the following (new stuff is written in bold)

<template>
<div class="messages">
<messages></messages>
<input-bar></input-bar>
</div>
</template>
<script>
import InputBar from ‘./components/InputBar’
import Messages from ‘./components/Messages’
export default {
components: {
InputBar,
Messages
}
}
</script>

You should see the Ramones yelling on your front end page.

I hear you saying “Ok, very funny… But if I write a message in my bar nothing happens. I want to yell together with the Ramones!”

You’re right. We need to add some communication between the input-bar component and the messages one. This implies managing the data that flows through our app :)

Managing Data in Vue

One thing I really love about Vue is how easy it is to separate concerns among presentation and data management.

In this How-to we use three kinds of entities:

  • Components, where you describe what your UI looks like and how it should react to user interactions. This is the only Vue-specific entity.
  • Stores, where you put the data you want Components to display. A store is a plain javascript object.
  • Services, where you put the logic that fetches or changes the data. A service is a plain javascript object.

We use services to fetch the data that we put into stores. Components read the data from the stores and display it. When data change in the stores, the components are instantly updated thanks to Vue’s reactive data-binding mechanism.

It´s KISS and we love it.

Now, let’s leave the services aside for a moment. We are going to implement communication among components as follows:

  • the messages component displays the content of the message store;
  • the input-bar component writes new messages to the message store;

The Messages Store and His New Friends

So, it seems the last brick we need to put is the store. Go ahead and create the src/store/messages.js (notice this is not a .vue file) and make it look like this guy here.

export default {
state: {
messages: []
},
sendMessage (content) {
this.state.messages.push({content, date: Date.now()})
}
}

A POJO with a method. Wow. Mind-blowing.

But now, let’s make our components aware of the store. To do that, we pass the store (or part of it) to the components as a prop, like here in src/App.vue

<template>
<messages
:messages="MessageStore.state.messages">
</messages>
<input-bar
:store="MessageStore">
</input-bar>
</template>
<script>
import InputBar from './components/InputBar'
import Messages from './components/Messages'
import MessageStore from './store/messages'
export default {
data () {
return {
MessageStore
}
},

components: {
InputBar,
Messages
}
}
</script>

Here

  • we imported the MessageStore and made it available as local data;
  • we passed the messages object to the Messages component;
  • we passed the store to the InputBar component.

Then, in src/components/Messages.vue

<script>
import Message from './Message'
export default {
props: ['messages'],
components: {
Message
}
}
</script>

We declare the messages prop (so that the component is able to receive it), and in src/components/inputBar.vue

<template>
<div class="input-box">
<input
type="text"
class="input-box-text"
placeholder="New message"
@keyup.enter="onEnter"
v-model="newMessage"
/>
</div>
</template>
<script>
export default {
props: ['store'],
data () {
return {
newMessage: null
}
},
methods: {
onEnter () {
if (this.newMessage.length > 0) {
this.store.sendMessage(this.newMessage)
this.newMessage = null
}
}
}
}
</script>

Here

Yo, you can now play with your pretty input field and see your messages appear.

Notice. Passing the whole store as a prop to a component is a very bad practice since it couples the component to the structure of the store. Only individual methods should be passed to the components, but this would have added a little bit of complexity in the code and we wanted to focus on our task at this stage of the How-to.

“I Feel Alone…”

That’s what my friend Anthony told me when I showed him my front end. He was right.

It’s totally lame to build an instant messaging tool to chat on your own. What if we had a back-end that could connect many instances of your new front-end and make them receive each other’s messages? It would be rad. Well, with Kuzzle.io this is quite easy.

Back-end Environment Setup

Notice. Be sure you have the ports 7511 and 7512 free on your host before you go on. Also, this section assumes you have docker and docker-compose installed on your computer. See here for installation details.

Create a new empty folder (let’s call it kuzzle) and put this docker-compose.yml file into it (don’t change the name of the file).

$ mkdir kuzzle
$ cd kuzzle
$ wget http://kuzzle.io/docker-compose.yml
$ docker-compose up

Once you see the racoons appearing on the standard output and the “Kuzzle Server Ready” message, you’re ready to go.

 ████████████████████████████████████
██ KUZZLE READY ██
████████████████████████████████████

Now my friend Anthony is very happy! He has a back-end with real-time pub/sub, storage and document search. Exactly what he needs for his instant messaging application. Keep this terminal alive to let Kuzzle run.

From now on we’ll talk to Kuzzle on localhost, but if your are on Windows or Mac, your Kuzzle URL will be the one of your docker machine.

Now let’s create a new index in Kuzzle by typing the following in a new terminal:

$ curl -X PUT http://localhost:7511/api/1.0/klack

The new index will be called klack.

In Kuzzle, an index is like a SQL database.

Using Kuzzle from the Front-end

To use Kuzzle from the front-end, we’ll use one of the available SDK (in our case, the Javascript one).

From the root folder of your project, type:

$ npm install --save kuzzle-sdk

Notice. You may need to restart your npm run dev after this if Webpack complains.

Then create the src/services/kuzzle.js file, like as follows:

import Kuzzle from 'kuzzle-sdk'

export default new Kuzzle('http://localhost:7512', {defaultIndex: 'klack'})

Here, we’re basically saying “connect a new instance of the Kuzzle SDK to the server on localhost:7512 with ‘klack’ as default index”.

Now, every time we import this service, we get this unique instance of the Kuzzle SDK (i.e. we treat it as a Singleton). Great! We created our first service.

Send out messages to Kuzzle

We’re now going to teach src/store/messages.js how to send messages to Kuzzle.

import kuzzle from '../services/kuzzle'
export default {
state: {
messages: []
},
sendMessage (content) {
let message = {content, date: Date.now()}
kuzzle
.dataCollectionFactory('messages')
.createDocument(message)

}
}

We changed our sendMessage method to send our message to Kuzzle via the SDK. Let’s see it in depth:

  • dataCollectionFactory(‘messages’) returns the KuzzleDataCollection object referencing the messages collection;

In Kuzzle, collections are like arrays of documents (or, if you prefer, like SQL tables). Notice that we referenced the ‘messages’ collection even if we never explicitly created it. This is because Kuzzle will create it for us once we put anything into it.

  • createDocument(message) tells Kuzzle to create a new document (containing the message) in the collection.

In Kuzzle, documents are like JSON object (or, if you prefer, like SQL rows).

This code just enabled us to send our messages to Kuzzle, which persists them as documents. Sweet. But how do we receive other user’s messages?

Receive Notifications from Kuzzle

One awesome thing about Kuzzle is that every time something happens on a document (e.g. a new document is created) a real-time notification is triggered. To receive our messages, we just need to subscribe to the notifications about the documents contained in the ‘messages’ collection.

Let’s create a new method, right after sendMessage, like as follows:

subscribeMessages () {
kuzzle
.dataCollectionFactory('messages')
.subscribe({}, null, (error, notification) => {
if (error) {
console.log(error.message)
return
}
this.state.messages.push({
...notification.result._source,
id: notification.result._id
})
})
}

Here, we subscribed to all the notifications being triggered within the ‘messages’ collection. Let’s see it in depth:

  • dataCollectionFactory(‘messages’) you know this guy;
  • subscribe() takes a filter (we leave it empty) an options object (we don’t need any) and a callback that is called with the notification from Kuzzle. If the notification contains no error, all we want to do is to take its content and store it in our messages array.

Note. All we do here is composing a new object with the attributes of _source (see the ES6 spread operator) plus the _id of the message.

Like that, every new message coming from Kuzzle will be instantly displayed by the Messages component.

Note. We do not push our message in the messages array within the sendMessage function anymore. That’s because we get notified by Kuzzle about our own messages (see the subscribeToSelf option for the subscribe method).

Last but not least, we need to start the subscription on the application startup so, go to src/App.vue and change it as follows:

export default {
data () {
return {
MessageStore
}
},
components: {
InputBar,
Messages
},
created () {
MessageStore.subscribeMessages()
}

}

Note. The created method is part of Vue’s Component Lifecycle Hooks and gets triggered just before the component is attached to the DOM.

Now, go ahead and open many windows at http://localhost:8080 and see how messages are broadcasted among all the instances.

Summing up, with one docker-compose command and 15 lines of JS code we just set up back-end data persistence and real-time notifications.

High fives, dude, my friend Anthony is happy!

A Little Exercise

You may have noticed that, even if you are able to receive messages from all your users, there’s no avatars. So you won’t know who wrote what. Not very convenient for an instant messaging app.

Try to Do It Yourself and see the solution on the next episode, starring… Properly Log Users In and Out!

[Bonus] Always Be Stylish

Notice. If you don’t give a damn about styles, this section is not for you.

So far we’ve been having fun playing with the basics of Vue and Kuzzle but my friend Anthony made me notice that the real Slack is more beautiful. He is right. But, know what? We can fix this. If you want to wait for Part 2 with a gorgeous UI, you can grab a ready-made Slack-like stylesheet we provided for you.

Webpack goes glamour

Don’t be shy and drop the ready-made stylesheet into src/style/global.css.

Now, we’ll have to teach Webpack how to handle CSS. Add this line on top of build/webpack.base.conf.js

var ExtractTextPlugin = require(“extract-text-webpack-plugin”);

Then add the following entry in the module.loaders object:

{
test: /\.css$/,
loader: ExtractTextPlugin.extract(“style-loader”, “css-loader”)
}

And add the following plugin object at the end of the file:

plugins: [
new ExtractTextPlugin(‘[name].css’)
]

Now you can go to your shell and stop the development server. Then execute:

$ npm install --save-dev css-loader style-loader extract-text-webpack-plugin
$ npm run dev

Now go to http//localhost:8080/ to see that… nothing has changed!

What?

It’s all set to enable Webpack being able to load stylesheets. This enables us to use require or import directives in our JS code to import CSS files. Yes. What we have just done is tell Webpack “Hey, when I import a CSS file, get it and add a link tag for that file in the head of the page”.

Let’s add the following line at the top of src/main.js to load the stylesheets:

import {} from ‘./style/global.css’

We just loaded our stylesheets via JS using the ES6 import syntax.

See ya!

--

--

Luca Marchesini
Building a Slack clone with Vue.js and Kuzzle.io

(very curious) Full Stack Web Engineer and Folktales Storyteller. Traveler and Parmesan cheese dealer.