Real-Time Data Transfer Using Vue and Socket.IO [Part 1 of 3]

Blog / Rob Hyndman / April 23, 2020

Since the mainstream adoption of the Internet in the early 90s, computing and network technology has advanced in leaps and bounds. The days of static HTML and flashing neon marquees are now an entire human generation behind us.

Did you ever sit on a website clicking the refresh button over and over for minutes on end waiting for new content to appear? It’s easy to imagine how difficult it would be to achieve your basic everyday online tasks on a website where the most advanced feature is a stream of sparkles following your mouse. It wouldn’t be impossible, but it would be extremely clunky and frustrating.

No, we want our modern online experiences to be snappy and responsive, dynamic yet simple, and above all else we really don’t want to have to wait for it. I know I’m not the only one who gives every site an arbitrary time limit to load before I back out and choose another!

Over this three-part series of posts, we’ll tackle these issues and hopefully by the time we’re done you will have some fundamental knowledge and hands-on experience with real-time data transfer and websockets. To start with we’ll discuss the benefits of real-time data transfer and consider some of the available technologies and possible use cases.

Afterwards we’ll build an app together step-by-step using Vue.js and Socket.IO. The app will feature two components which illustrate the use of real-time data transfer in different ways—a chat room, and a simple cooperative multiplayer game. We’ll also implement a basic server to control the data transfer between clients.

This first post will cover setting up a simple server and client, and adding websocket functionality to both. The second post in the series will focus on the chat room. Finally, the third post will build upon the techniques we learned to create a simple multiplayer game “Fill Frenzy!”.

Fig.1 – A preview of Fill Frenzy!

Why is Real-Time Data Transfer Useful?

The main benefit to real-time data transfer is in the name. Real-Time. This means that data is instantly available and instantly retrieved, providing minimal latency on information sharing. Real-Time apps are everywhere these days and it is not hard to see why—there are a multitude of different applications for this technology, and they are all vastly improved by the ability to communicate in real-time.

Social Media and Chat

  • Communicate instantly
  • Notifications for new posts
  • Notifications for when someone is typing
  • Acknowledgements for message receipt and read

Games

  • Multiplayer games
  • Low latency for updates

Collaborative editing

  • Multiple users
  • Live edits and updates

Taxi services

  • Real-Time location of drivers

Medicine

  • Trauma or emergency situations with changing vitals and treatments

Finance

  • Accurate real-time charts
  • Instant changes in stock values

What is a Socket and Why Use Them?

Sockets are a connection between two points on a network or over the internet. The connection could be client-to-server, client-to-client, or even server-to-server. A socket connection is persistent and able to hold a connection between the two ends until closed, allowing two-way communication of data with minimal latency.

To help understand how sockets work, imagine a telephone switchboard. You have a person calling from one telephone (device A), trying to reach another telephone (device B). The operator takes a cable and plugs it into the line that will connect the two telephones. The socket is a representation of that cable.

Fig.2 – “Telephone operator at work” (source:
Wikimedia Commons)

There are two other techniques to consider for real-time data transfer between devices. The first is Server-Sent Events (SSE), which works by forming a connection between a server and client, where the server pushes out events as they happen in real-time. This is fantastic for a notification service, but the downside here is that this is one-way communication, so the client is unable to communicate with the server in real-time.

The other technique, polling, allows two-way communications between the server and the client. The downside is that polling happens on set time intervals, so the data is not likely to be real-time. Sockets are looking like the best option for what we need.

Real-Time App: Fill Frenzy!

Now that you have some understanding of what a websocket is, and generally how it works, let’s move on to building the app. Below you will find a series of easy to follow step-by-step instructions split into three sections:

  • The installation process,
  • Creating the server, and
  • Building the app

Installation Process

  1. Install Node.js.
  2. Using the Command Prompt or Terminal, install the Vue command line interface tools with the command npm install -g @vue/cli. Restart your console to load the new changes.
  3. Create a new vue project with the command vue create real-time-demo. Follow the prompts and choose to manually select features. Change the selected features to ensure that only vuex is selected, and place the configuration in package.json.
  4. ? Please pick a preset: Manually select features
    ? Check the features needed for your project:
    O Babel
    O TypeScript
    O Progressive Web App (PWA) Support
    O Router
    X Vuex
    O CSS Pre-processors
    O Linter / Formatter
    O Unit Testing
    O E2E Testing 
  5. Enter the project directory and install server and socket dependencies with the commands:
  6. cd real-time-demo
    npm install --save express socket.io socket.io-client
  7. Install Moment.js, which will help us parse timestamps:
  8. npm install --save moment
  9. Install Vuetify:
  10. npm install --save vuetify
    npm install --save-dev vue-cli-plugin-vuetify vuetify-loader
    npm install --save-dev sass sass-loader
  11. Start serving the app with the command npm run serve. If the app compiled successfully, you should see a message similar to the following.
  12. DONE Compiled successfully in 120ms
    
      App running at:
      - Local:   http://localhost:8080/
      - Network: http://192.168.1.1:8080/
  13. Navigating to either the Local or Network address will show the Vue HelloWorld app. Take note of the “Network” IP address (in this case 192.168.1.1), as we’ll need it later to allow users on the local network to connect.

Create the Server

This server will act as a controller for all the data that is transferred between clients. Every time a new client connects to the server, the server will ensure that the client is kept up-to-date. Follow the steps below to set up the server.

  1. Open the app folder in your favourite IDE. Visual Studio Code is great to use if you don’t have a favourite IDE.
  2. Create a new file named server.js in the project root with the following code.
  3. const express = require('express');
    const http = require('http').Server(express);
    const port = 3030;
    
    http.listen(port, () => {
      console.log('Server started on port', port);
    });

    The server runs using the most basic of features provided by Express and listens for network traffic on port 3030.

    To confirm this is working, open a terminal in the project directory and run the command node server, which should output the message Server started on port 3030. Stop the server by pressing ctrl+c.

  4. Add the Socket.IO library to server.js to allow the server to listen for communications from our client.
  5. const express = require('express');
    const http = require('http').Server(express);
    const socketio = require('socket.io')(http, { pingTimeout: 60000 });
    const port = 3030;
    
    socketio.on('connection', (socket) => {
      // new socket connected
    
      // listen for a 'message' event
      socket.on('message', (eventData) => {
        // attach the current time
        eventData.processed = Date.now();
    
        // send the message back to the client
        socket.emit('message', eventData);
      }
    });
    
    http.listen(port, () => {
      console.log('Server started on port', port);
    });

    Setting the value of the pingTimeout property to 60 seconds should allow all browsers to maintain a constant connection to the server without timing out. We also create a listener which waits for the connection event to occur before triggering a function for handling a new socket connecting to the server.

    For now, we’ll handle a simple test by listening for a message and attaching a timestamp to it before returning the message to the client.

Create the App

This app will be the web page that users interact with. It will consist of the two main components mentioned earlier: the chat room and the game. For now, let us focus on getting the basic framework of the app working, and we’ll cover the rest later in this post.

Initialise Vuetify

Vuetify is a library which includes some very useful Material Design Components, allowing us to build our interface without creating elements from scratch.

  1. Create src/plugins/vuetify.js with the following code.
  2. import Vue from 'vue';
    import Vuetify from 'vuetify/lib';
    
    Vue.use(Vuetify);
    
    export default new Vuetify({});
  3. Open src/main.js and import and load the vuetify plugin
  4. import Vue from 'vue';
    import App from './App.vue';
    import store from './store';
    import vuetify from './plugins/vuetify';
    import moment from 'moment';
    
    Vue.config.productionTip = false;
    Vue.prototype.moment = moment;
    
    new Vue({
      store,
      vuetify,
      render: (h) => h(App)
    }).$mount('#app');

Initialise Sockets

Now let’s make sure the client can connect to and communicate with our server by creating a little plugin using Socket.IO.

  1. Create src/plugins/socketio.js and add the following code.
  2. import io from 'socket.io-client';
    
    let socket = undefined;
    const localIP = 'localhost';
    const networkIP = '0.0.0.0';
    const port = 3030;
    const networkConnection = false;

    First off we’ll import the Socket.IO client library, and initialise some properties to help with our connection.

    Replace the value of networkIP with the IP printed to the console by Vue (remember the one I said to take note of?). Changing the value of networkConnection to true will tell the socket to connect over the network, allowing other users to connect.

    function initialiseSocket() {
      const url = networkConnection ?
        `http://${networkIP}:${port}` :
        `http://${localIP}:${port}`;
    
      socket = io(url);
    }

    Next create a function that will create a socket connection to either a local or network server.

    When the initialiseSocket function is called the value of the socket variable will be set, and the socket will be connected to the server. This triggers the connection event that we are listening for on the server.

    export function addEventListener(event) {
      if (!socket) {
        initialiseSocket();
      }
    
      socket.on(event.type, event.callback);
    }
    
    export function sendEvent(event) {
      socket.emit(event.type, event.data);
    }

    Finally, create a function to listen for incoming events, and a function to emit events to the server.

Setup Components

Next we need to remove the HelloWorld content from the app, and replace it with a new component that will house our content later, but for now will work as a little test for us to track the time it takes for our messages to hit the server and come back to the client.

  1. Delete the src/components/HelloWorld.vue component.
  2. Create a src/components/RealTimeDemo.vue component with the following layout. This is where we’ll begin to take advantage of the pre-built components that Vuetify offers (note the tags prefixed with v-).

    If you aren’t familiar with Vuetify, don’t worry—these are just fancier versions of the regular HTML tags you’ll be used to. For example, v-container is just a div with some styling built in, and v-btn is the same as input type="button" with some styling too. In any case, the Vuetify components aren’t related to the real-time data transfer, but they do help us to quickly build a nice looking interface!

  3. <template>
      <v-container>
        <v-row>
          <v-col cols="6">
            <v-text-field
              v-model="textInput"
              @keypress.enter="sendMessage()"
            ></v-text-field>
            <v-btn @click="sendMessage()">
              Send
            </v-btn>
          </v-col>

    The template will be a page with two columns. The first column contains a text field where the user can enter a message, and a button that they can use to send the message to the server.

          <v-col cols="6">
            <v-card class="pa-4">
              <v-simple-table>
                <template v-slot:default>
                  <thead>
                    <tr>
                      <th>Message</th>
                      <th>Sent</th>
                      <th>Processed</th>
                      <th>Received</th>
                    </tr>
                  </thead>

    The second column will contain a table that will have four headings relating to our message and the time it takes to reach the server and return to the client.

                  <tbody>
                    <tr
                      v-for="output in serverOutput"
                      :key="output.text"
                    >
                      <td>{{ output.message }}</td>
                      <td>{{ formatTime(output.sent) }}</td>
                      <td>{{ formatTime(output.processed) }}</td>
                      <td>{{ formatTime(output.received) }}</td>
                    </tr>
                  </tbody>
                </template>
              </v-simple-table>
            </v-card>
          </v-col>
        </v-row>
      </v-container>
    </template>

    The table body will show the details of each message sent, and the times that it reached each point of its journey.

    <script>
    import * as socketio from '../plugins/socketio';
    
    export default {
      name: 'RealTimeDemo',
      data: () => ({
        textInput: '',
        serverOutput: []
      }),

    The JavaScript section of this component should import the Socket.IO plugin that we wrote. We’ll also create variables—one for user text input and the other an array to hold server output.

      mounted() {
        socketio.addEventListener({
          type: 'message',
          callback: (message) => {
            message.received = Date.now();
            this.serverOutput.push(message);
          }
        });
      },

    Before the app starts running, we need to register some event listeners. In this case, we are only interested in the message event that the server sends to the client. Whenever a message event is received from the client, we take the object it sends us and attach to it the time of receipt and store it in our array of server outputs.

      methods: {
        sendMessage() {
          socketio.sendEvent({
            type: 'message',
            data: {
              message: this.textInput,
              sent: Date.now()
            }
          });
    
          this.textInput = '';
        },
        formatTime(timestamp) {
          return this.moment(timestamp).format('h:mm:ss.SSS');
        }
      }
    }
    </script>

    Next we create two methods. sendMessage handles packing our message into an object with a timestamp to send to the server, and formatTime simply gives us an easy-to-read timestamp.

  4. Open src/App.vue and import and use the new component like so:
  5. <template>
      <v-app>
        <real-time-demo />
      </v-app>
    </template>
    
    <script>
    import RealTimeDemo from './components/RealTimeDemo';
    
    export default {
      name: 'app',
      components: {
        RealTimeDemo
      }
    }
    </script>

Time to test!

Now, let’s see how our app handles.

  1. Start the server with the command node server.
  2. Open the app in your browser at localhost:8080.
  3. After sending a message, you should see the time details appear in the table on the right.
  4. Try altering the configuration of src/plugins/socketio.js to allow for a network connection, and test from another device on your network to see how the times differ.

Conclusion

In this post we created a server that can listen for connections, as well as receive messages from and forward messages to the client. We also created a client that can send messages to the server and listen for when the server replies. All of this happens in real-time! We even have evidence of how quickly the messages we send traverse the network.

The next post in this series will focus on taking what we have created so far, and turning it into a multi-user chat room. I hope to see you then!

You can find part 1 of this project on GitHub.


Header image courtesy of Unsplash