Andreas' Blog

Adventures of a software engineer/architect

Building realtime apps with Vue and nodeJS

2018-04-18 7 min read anoff

Wanting to build a simple SPA as a side project I struggled with things that might annoy a lot of newcomers that do not want to go full vanilla. Which web framework, which style library, which server framework - and more importantly how does it all work together?

In this post we will put together a bunch of great tools out there to build a realtime web app with a few single lines of code. A quick introduction into the tools that will be used:

Set up a basic Node.js server

Hello World Node.js

The first thing we’ll do is set up a node server to provide a backend. Using the servers.js library a basic API service can be build with a few lines of code.

# in any empty directory
npm init # initialize npm project
npm install server

Creating a Hello World example:

// server.js
// load the server resource and route GET method
const server = require('server')
const { get } = require('server/router')

// get server port from environment or default to 3000
const port = process.env.PORT || 3000

server({ port }, [
  get('/', ctx => '<h1>Hello you!</h1>')
])
  .then(() => console.log(`Server running at http://localhost:${port}`))

Running the code with node server gives the following output and the website showing Hello World! will be reachable at localhost:3000

Server running at http://localhost:3000

For easier development install npm install nodemon in the project and change the start command to:

// package.json
"scripts": {
  "start": "nodemon -i myapp/ server.js"
},

💡 If you’re struggling take a look at this code for reference

Initialize Vue.js project

The easiest way to set up a vue project is to use the vue-CLI which is available via npm install -g vue-cli. To initialize a project using webpack as a bundler run

vue init webpack myapp

Answer the questionnaire with the default question or disable tests you do not want to implement. I chose not to install any test frameworks for this tutorial.

Webpack comes with it’s own development server with hotreloading functionality so you see changes in the browser right away. Try it out by starting the server with npm run dev (in the myapp/ directory) and opening the Vue.js template at localhost:8080

Vue.js template project > output of the webpack dev server npm run dev

Vue.js template project > Template Vue.js project at http://localhost:8080

When modifying the Vue.js components the web page will automatically reload

// myapp/src/components/HelloWorld.vue

// chnage the script content to
...
<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to MY first Vue.js App'
    }
  }
}
</script>
...

By simply saving the file the development server will propagate the changes to any open browser window which will automatically reload to

modified Vue.js template project

Modified template with custom message

💡 If you’re struggling take a look at this code for reference

Adding Material Design library

To install vue-material run the following command in the Vue.js directory myapp/

npm install vue-material@beta --save

Add the following lines to myapp/src/main.js to load the vue-material components into the app.

import VueMaterial from 'vue-material'
import 'vue-material/dist/vue-material.css'
import 'vue-material/dist/theme/black-green-light.css'

Vue.use(VueMaterial)

ℹ️ You might have to restart the dev server for this new plugin to take effect

Create a new Vue.js component making use of several vue-bootstrap components like the app container.

<!-- myapp/src/components/Chat.vue-->
<template>
<div class="page-container">
    <md-app>
      <md-app-toolbar class="md-primary">
        <div class="md-toolbar-row">
          <span class="md-title">My Chat App</span>
        </div>
      </md-app-toolbar>
      <md-app-content>
        <md-field :class="messageClass">
          <label>Messages</label>
          <md-textarea v-model="textarea" disabled></md-textarea>
        </md-field>
        <md-field>
          <label>Your message</label>
          <md-input v-model="message"></md-input>
          <md-button class="md-primary md-raised">Submit</md-button>
        </md-field>
      </md-app-content>
    </md-app>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      textarea: "dummy text\nblup\ndummy text"
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-app {
  height: 800px;
  border: 1px solid rgba(#000, .12);
}
.md-textarea {
  height: 300px;
}
</style>

To load the new component modify the router at myApp/src/router/index.js

// change HelloWorld -> Chat
import Vue from 'vue'
import Router from 'vue-router'
import Chat from '@/components/Chat'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Chat',
      component: Chat
    }
  ]
})

Basic chat app

💡 If you’re struggling take a look at this code for reference

Bring in websockets

For the following development the web application will consume from two different endpoints. The webpack-dev-server sends the web app sources (HTML, CSS, Javascript) and the node server will supply the socket-io endpoint. This is typically not something you want to do in production but since we want both the node server and Vue frontend to be hot reloaded we need two systems - webpack and nodemon.

development setup

frontend: vue-socket.io

For the Vue app to communicate with the websocket backend the socket.io library needs to be installed into cd myApp/

npm install vue-socket.io

With the node backend running on port 3000 modify your vue application in myApp/src/main.js to connect to the backend

import VueSocketIO from 'vue-socket.io'

Vue.use(VueSocketIO, 'http://localhost:3000')

To bring some very basic functionality into the app we will show messages that were send from other instances in a list and add the ability to send messages. For sending messages we need to give the Submit button an action once it is triggered by adding a v-on:click method

<md-button class="md-primary md-raised" v-on:click="sendMessage()">Submit</md-button>

The sendMessage() function and the socket interactions are specified in the <script> tag

<script>
export default {
  name: 'Chat',
  data () {
    return {
      textarea: '',
      message: '',
      count: 0
    }
  }, sockets:{
    connect () {
      console.log('connected to chat server')
    },
    count (val) {
      this.count = val.count
    },
    message (data) { // this function gets triggered once a socket event of `message` is received
      this.textarea += data + '\n' // append each new message to the textarea and add a line break
    }
  }, methods: {
    sendMessage () {
      // this will emit a socket event of type `function`
      this.$socket.emit('message', this.message) // send the content of the message bar to the server
      this.message = '' // empty the message bar
    }
  }
}
</script>

backend: socket-io / server.js

Server.js already comems with socket-io bundled into it. The only thing to do in the backend to enable a basic chat operation is to react to a message event sent from the UI and propagate this to all connected sockets.

// modify server.js to include the socket methods
const { get, socket } = require('server/router')

...

server({ port }, [
  get('/', ctx => '<h1>Hello you!</h1>'),
  socket('message', ctx => {
    // Send the message to every socket
    ctx.io.emit('message', ctx.data)
  }),
  socket('connect', ctx => {
    console.log('client connected', Object.keys(ctx.io.sockets.sockets))
    ctx.io.emit('count', {msg: 'HI U', count: Object.keys(ctx.io.sockets.sockets).length})
  })
])
  .then(() => console.log(`Server running at http://localhost:${port}`))

After running npm start in the server directory the server will now create logs for every web page that gets opened. It logs the list of currently open sockets.

Note there is no disconnect event registered yet in this tutorial so the count event will only be emitted when a new website connects.

Server logs for connecting clients

Showtime 🍿

Running the demo in two browsers/separate devices in the same network will look like this. It is a very, very, very basic but totally anonymous chat system.

Very basic chat system

You can find a repository on github containing this demo code.

I hope this blog helped you:

  1. set up an easy node server
  2. bootstrap a Vue project with vue-cli
  3. get fancy UI elements in Vue using material design
  4. integrate websockets to provide realtime communication

What to do next:

  • add tests to backend/frontend
  • store state/sessions in frontend
  • possibly add authentication
  • improve UI (e.g. register enter button on message bar)

Feel free to leave a comment or reach out on twitter 🐦

comments powered by Disqus