The WebSocket protocol is an extension to the HTTP ecosystem which allows to create live connections between a web server and a web browser, enabling web applications to efficiently exchange data in real-time without the overhead of conventional HTTP connections. Node.js is perfectly suited for writing WebSocket applications, and this tutorial explains how it all works.

Where WebSocket connections beat HTTP connections

The HTTP protocol obviously does a great job at enabling data exchange between a webserver and a browser, hence the extraordinary success of the World Wide Web. So why does the Web need another protocol? It’s two aspects that are relevant here: bi-directionality and real-time.

HTTP connections: neither bi-directional nor real-time

The “conventional” Web, built on HTTP, is based on a client-server model with the client (the web browser) in the lead.

Conventional HTTP web servers do nothing on their own without being explicitly asked by a client. Getting some kind of information - for example, the current price of a stock, in the form of a web page with stock market information - requires the browser to actively ask the server for that information, e.g. by issueing a GET request to the web page at, say, http://stocks.example.com/company/apple-inc.

This is only triggered by a human user who decides to visit this page. If this user wants to learn the most recent stock price again at a later point in time, he or she has to reload the page.

If the webpage wants to supply the most recent stock price to the user regularly without requiring the user to manually reload the page, then workarounds exist. The most common workaround is to run some JavaScript code in the webpage which regularly “polls” a webservice from the server which responds with the current stock price, via AJAX. If the new stock price is different from the one on the page, then the client-side JavaScript code can update the webpage content and present the new price to the user.

This improves the interactivity of the site, but it is not optimal: If stock price updates need to be reported to the user as soon as possible, the JavaScript code needs to query the webservice with high frequency - for example, every 5 seconds. And if the requirement is to inform the user about a stock price change no later than 1 second after the change occured, then a new request must be send to the server every second.

But that’s not a real solution to the requirement of getting the info from the server proactively - you are just asking very often, which isn’t exactly efficient. Imagine that there is no stock price change for 10 minutes. That’s 600 HTTP requests from the client to the webserver with no useful results at all. That is wasting a lot of traffic and computing resources on both the client and the server.

Also, there’s a limit to the frequency at which the client can ask the server for new content. Using AJAX, you can get near-time updates from the server to the client, but doing real-time updates efficiently is out of the question.

This is why until now, complex applications which update many different information on the screen fluidly and in real-time are seldom seen on the web and are usually implemented as full-fledged desktop applications. But the WebSocket protocol is about to change this.

WebSocket connections: putting the webserver in the lead

The important conceptual change that the WebSocket protocol brings to the table is that it allows the client to establish a connection with the web server which a) stays open as long as the web page is opened in the browser, and which b) the web server can actively use to send data to the client whenever the server wants to, without being asked by the client to do so.

Thus, in addition to the client-server model of HTTP, we now have a model of a truly bi-directional connection, with both partners of the connection being equal.

As a result, this allows for data exchange that is much more efficient and happens real-time. In our stock price example, the server can “push” a stock price update over the connection as soon as it occurs, and only if and when it occurs. If there is no stock price update for 10 minutes, then the exchanged data during these 10 minutes is zero. And when the update finally occurs, the client learns about it immediately, and not when it happens to ask.

How WebSocket connections work

Conceptually, the WebSocket protocol is an extension to HTTP which allows clients to “upgrade” an HTTP connection with an additional bi-directional connection which remains established, like this:

  • Client opens HTTP connection to server and asks for document
  • Server responds with an HTML document
  • HTTP connection is closed
  • JavaScript code in the HTML document opens another HTTP connection in which it asks the server to upgrade this connection to a WebSocket connection
  • A WebSocket connection between client and server is established and remains open for sending and receiving data in both directions

Technically, a WebSocket connection is simply a TCP connection on port 80, just like normal HTTP connections are - with the difference that client and server treat the connection in a special way.

Creating a WebSocket connection from an HTTP connection requires knowledge about the details of the HTTP and WebSocket protocols, but luckily, easy-to-use libraries exist which provide a nice and straightforward abstraction of these details both on the server and the client side. We will look at how to use these in Node.js now.

How to use WebSocket connections in your web application

By now we learned that the client-side JavaScript code on a webpage needs to intitiate a WebSocket connection, and the server-side code needs to handle such a request accordingly. Thus, specialised client and server code is neccessary.

How do we get there?

When working with Node.js, it’s not difficult at all. The most straightforward and efficient approach is to use a JavaScript library which provides the neccessary methods for both sides - a client part which is executed by the user’s web browser, and a server part which runs within an existing Node.js HTTP server.

The most mature and most popular library for this is Socket.io. Let’s use this to write a simple WebSocket stock price ticker application.

As said, we need to take care of both the client side and the server side to get a WebSocket application up and running. We start with the server side, which we’ll of course implement using Node.js.

From a Node.js perspective, Socket.io is simply an NPM library. However, we are not going to install it just yet. Socket.io doesn’t work on it’s own - it is not a full-fledged Node.js webserver or web application framework. It needs a webserver or framework to integrate with, and we’ll set that up first, using the popular Node.js web framework Express.js.

To do so, create a new project folder called websocket-test, and create the following package.json inside:

{
  "name": "websocket-stock-ticker",
  "version": "0.0.1",
  "description": "WebSocket Stock Ticker",
  "dependencies": {}
}

Then, pull in Express.js via NPM: npm install --save express.

You can now create a very basic Express.js based HTTP webserver, in file index.js:

var app = require('express')();
var http = require('http').Server(app);

app.get('/', function(req, res) {
  res.sendFile(__dirname + '/index.html');
});

http.listen(8000, function() {
  console.log('Listening on *:8000');
});

This creates an HTTP webserver listening on port 8000, which serves file index.html through the Express.js route handler when requested at path /.

However, file index.html does not yet exist, so let’s create that in the same folder:

<!doctype html>
<html>
  <head>
    <title>Live stock price ticker</title>
  </head>
  <body>
    Stock price: <span id="stockprice">no data yet</span>
  </body>
</html>

If you start the application with node index.js and open address http://localhost:8000 in your browser, you are going to see a very basic web page which shows Stock price: no data yet as its content.

We have laid the foundation for our live stock price ticker application, but that no data yet part of the web page needs to be updated regularly with the most current stock price, and this of course will be done through a WebSocket connection between the page on the client side and our Node.js server.

As stated above, it is the client that has to initiate a WebSocket connection. Right now, we do not yet have any JavaScript in our web page which could do that, so let’s change that.

To do so, we need to integrate the client-side part of Socket.io into our page. For a real and production-ready environment, we would probably want to download and integrate this library - called socket.io-client - ourselves, but for getting a quick prototype of our application up and running, it’s sufficient to let our server-side code serve the client-side library automatically.

This is achieved by installing and integrating the server-side part of Socket.io into our Node.js application.

Installation is done by running npm install --save socket.io, and afterwards, we can integrate it with our Express.js server:

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res) {
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
  console.log('A new WebSocket connection has been established');
});

http.listen(8000, function() {
  console.log('Listening on *:8000');
});

This pulls a Socket.io object into our code in variable io. It is immediately integrated with our Express http server object - Socket.io knows how to hook into an Express.js server out of the box, so we don’t need to do any integration work ourselves.

This integration also results in the creation of a new Express.js route - when our webserver is requested at http://localhost:8000/socket.io/socket.io.js, it serves the client-side Socket.io library. Again, this is nothing we have to do ourselves in our own code. The line var io = require('socket.io')(http); takes care of this automatically and behind the scenes.

What we do have to code ourselves is to add an event listener which handles events from the WebSocket connection.

For now, we only react to the event that is triggered when a new client establishes a WebSocket connection. To do so, we attach an anonymous function to the connection event emitted my the io object. We use it to generate log output.

Learn all about Event Listeners and Event Emitters from my new book, The Node Craftsman Book. It comes bundled with The Node Beginner Book, and both together are now available via Leanpub for only $9.

If you restart the Node.js server and again load page http://localhost:8000 in your browser, nothing new happens.

This is because our web page does not yet open up a WebSocket connection. We need to code this now, in file index.html:

<!doctype html>
<html>
  <head>
    <title>Live stock price ticker</title>

    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>

    <script>
      $(function () {
        var socket = io();
      });
    </script>

  </head>
  <body>
    Stock price: <span id="stockprice">no data yet</span>
  </body>
</html>

As you can see, we load the Socket.io client library which is now provided by the Socket.io server library. Note that we also pull in jQuery - this is not strictly neccessary, but it makes it easier to manipulate the web page content.

If again you reload the web page (no need to restart the server here), you will now see “A new WebSocket connection has been established” being outputted by the server.

With this, everything is in place to actively push data from the server to the web page. Let’s do this now.

Again, we start on the server side. We do not have real stock prices, so we will just generate a new random stock price value every second for each client that connects, and push it out to all connected clients:

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res) {
    res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket) {
    console.log('A new WebSocket connection has been established');
});

setInterval(function() {
  var stockprice = Math.floor(Math.random() * 1000);
  io.emit('stock price update', stockprice);
}, 1000);

http.listen(8000, function() {
    console.log('Listening on *:8000');
});

The key function here is io.emit - Socket.io tracks all client connections internally, and emit sends out a message to all these clients. This is the most simple way to get data from the WebSocket server to the WebSocket clients, but for our demo application it’s good enough.

Each message sent via emit has an event name - in our case, that’s stock price update - and a value, which is the new stock price we calculated. We can make up event names on the fly - we just need to make sure that clients work with the same event names to make them useful. And we can send any basic JavaScript values, like strings, booleans, numbers, arrays, objects etc.

We don’t need to do anything on the client side to make the WebSocket client receive these messages, but we need to extend our code if we want to make the client do anything useful with it. In this case, let’s update the stock price display whenever a new message arrives. By combining Socket.io and jQuery, this is difficult:

<!doctype html>
<html>
  <head>
    <title>Live stock price ticker</title>

    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>

    <script>
      $(function () {
        var socket = io();

        socket.on('stock price update', function(stockprice) {
          $('#stockprice').text(stockprice);
        });

      });
    </script>

  </head>
  <body>
    Stock price: <span id="stockprice">no data yet</span>
  </body>
</html>

As you can see, we have added an event listener on the socket object right after we created it. This listener reacts to all received messages whose event name is stock price update. Attached to the event listener is an anonymous function which receives the value part of the message whenever the listener triggers it - in our case, we know that this value is the latest stock price.

We then use this value to update the text content of the <span> element with id stockprice, using jQuery.

If you reload the updated page, you still see the no data yet text for a moment, but then the stock price updates are reflected on the page each seconds. If you open multiple browser windows or tabs, you should see the same stock price value being displayed and updated at the same time.

An update cycle of 1000 ms still feels very “normal HTTP-ish” - let’s prove how WebSocket connections can really make server-side updates to web page contents feel much smoother, by changing our server code to this:

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res) {
    res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket) {
    console.log('A new WebSocket connection has been established');
});

setInterval(function() {
  var stockprice = Math.floor(Math.random() * 1000);
  io.emit('stock price update', stockprice);
}, 50);

http.listen(8000, function() {
    console.log('Listening on *:8000');
});

The only change is the setInterval timing, from 1000 ms to 50 ms. When you restart the server and again open the web page in multiple browser windows, you can see the efficiency and performance of WebSocket applications in action.


Learn more about writing web applications using Node.js with The Node Beginner Book - the first part of this step-by-step Node.js tutorial is available for free!