Building realtime apps with Vue and nodeJS
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:
- Node.js: Javascript runtime to build server applications
- Vue.js: A webapp framework
- Material Design: Set of styled web components by Google using the vue-material library
- socket.io: Client & server library for websockets
- servers.js: An opinionated server framework for Node.js based on express
Set up a basic Node.js server
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
> output of the webpack dev server npm run dev
> 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 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
}
]
})
💡 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.
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.
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.
You can find a repository on github containing this demo code.
I hope this blog helped you:
- set up an easy node server
- bootstrap a Vue project with
vue-cli
- get fancy UI elements in Vue using material design
- 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 🐦