hckr.fyi // thoughts

Getting Started with Node.JS and Socket.io for Real Time Web Applications

by Michael Szul on

I occasionally mention work projects on this site, and many of the posts that I write are directly influenced by things that I do at work. Currently, I'm spearheading a project rewriting the entire medical education technology suite for my institution's undergraduate medical education program. We started by aggregating the data from several systems into a single database with multiple schemas, and then skinned to previous learning management system (LMS) to provide a mobile-friendly experience, with an updated look-and-feel.

As a part of this process, we re-engineered the application portal with a card-based layout--each card being both an entry point into another application in the suite, as well as actionable items to limit steps and friction.

Several of these cards have data that can be based on timely interactions. For exam, "open exams" for currently open tests, or exam statistics to evaluate overall performance. During in-class exams, students need to know when an exam opens, and faculty need to know progress. For a timed exams, students would have to refresh the page to see when an exam is finally open, and faculty members would have to refresh the page to see the statistics update.

This is not idea. What we really need is real-time data, which means building a real-time application feed.

This post is the first on a series about building real-time application feeds in Node.JS with Socket.io. Maybe we'll even do a few videos on it, as well.

First, let's talk about getting started. I'm going to assume for this series that you're front-end and back-end are both JavaScript (or TypeScript). We'll need to install Socket.io.

npm install socket.io --save
    npm install @types/socket.io --save-dev
    

We're going to use Express as our back-end framework, and Socket.io and Express will run on the same port, so we'll use the http package as the intermediary. If you're entry point is an index.ts file (we're using TypeScript here), it might resemble something like this:

import * as express from "express";
    import * as http from "http";
    import * as socket  from "socket.io";
    
    const app = express();
    const server = new http.Server(app);
    const io = socket(server);
    
    server.listen(process.env.PORT || 3000, () => {
        console.log(`Application listening on port ${process.env.PORT || 3000}!`);
    });
    

We can add Socket.io pretty easily. You can put this before the server.listen() along with any of your Express routes:

io.on("connection", (socket) => {
        console.log("A user has connected to the socket!");
        socket.on('disconnect', () => console.log('A user has disconnected from the socket!'));
    });
    

Here we're just setting up a connection event when someone connects to the Socket.io infrastructure. Inside, we simply log a message, and then we set up an event when someone disconnects from the socket.

Now we need to set up things on the front-end. You'll need to include a Socket.io client. One comes with the NPM package:

<script type="text/javascript" src="{{basePath}}/node_modules/socket.io-client/dist/socket.io.js"></script>
    

Then, you can set up the front-end logic for connecting inside of a <script> tag:

let socketUrl = "{{basePath}}";
    if(document.location.href.indexOf("localhost") != -1) {
        socketUrl = "http://localhost:3000";
    }
    const socket = io(socketUrl);
    

In the code above, we default the socketUrl to the base path. If the current URL is localhost, however, we set the socketUrl to the localhost URL and port that is running.

Once we know what the URL is, we create a socket variable by passing the URL into the io() function.

This is all you need to get started. Spin up your Express instance, and then go to the localhost URL. You should see the "A user has connected to the socket!" string logged in the output window.

Now this does absolutely nothing of value, but it shows you how to create a real-time connection between a client and server. You can then begin to write real-time connection logic using on() and emit(). We'll begin to look at these in future posts as we build out real-time functionality, but as a quick example, you could set up connections like the following:

Client:

socket.on("open_exams", (data) => {
        //Receives data automatically from the server when "open_exams" is emitted to.
    });
    

Server:

io.on("connection", (socket) => {
        console.log("a user connected");
        const hasChanged = await haveExamsChanged();
        if(hasChanged) {
            socket.emit("open_exams", { refresh: true });
        }
    });
    

On the server side, hasChanged checks to see if any exams have since changed their status. If so, we "emit" to the "open_exams" channel, returning a single object telling the client that the refresh value is true.

We'll see how this code evolves to actually do something useful in future posts.