Realtime with socket.io
Learning Objectives
Students will be able to: |
---|
Add and configure socket.io in a Node/Express app |
Send messages from a client to a socket.io server and vice versa |
Listen for messages on both the socket.io server and client |
Optionally send a data payload with a message |
Implement realtime communication between browser and server in a Node/Express app |
Roadmap
-
Intro to socket.io
-
Review the Starter App:
realtime-circles
-
Configure socket.io on the Server
-
Configure socket.io in the Client (browser)
-
Display Circles in Realtime
-
Clear the Display (practice)
-
Deploy to Heroku
-
More Big Fun? Track Players!
Intro to socket.io

Using the above diagram we can answer the following regarding the interaction between a browser and a web server:
-
What is bidirectional communication?
-
What is realtime communication?
How cool would it be to incorporate these concepts in our apps!
❓ What type of applications are made possible by realtime bidirectional communication between clients and a server?
What Technology Enables Realtime Communications?
We just identified some of the cool apps that leverage realtime communications.
So far in SEI, we've learned quite a bit about the HTTP protocol.
❓ How does HTTP communications contrast with bidirectional realtime communication?
As we learned in week one, it was the demand for modern web applications that led to the development of...

Fortunately, the HTML5 specification also included the ability to "upgrade" a HTTP connection upon request of the client.
This connection upgrade results in the switch to a protocol that supports bidirectional/realtime communication - the websocket protocol.
Working with websockets natively is not terribly difficult, however, it can take a bit of code to handle automatic reconnects, etc. But as we've learned that using libraries can make us more productive developers.
socket.io is a JavaScript library that wraps the websocket protocol and makes it easier to implement the realtime, bidirectional communication we seek.
Basic socket.io Architecture

-
socket.io Clients & the socket.io Server send "messages" to each other...
-
and both Clients & the Server can listen and react to those "messages".
What are "messages"?
socket.io is all about sending and responding to messages.
A message is a simple string identifier that we get to define,
for example:'login'
, or 'move-player'
Just like when naming functions, it's best to use identifiers for our messages that reflect their purpose.
When sending a message, we can optionally send data that will be received by the listeners for the message. For example, when sending a 'move-player'
message, we might send the following JS object:
{
from: 'r8c3',
to: 'r9c4'
}
❓ Review Questions
Here's a few review questions before we take a look at our Starter App:
-
Explain bidirectional communications.
-
What protocol, introduced with HTML5, enables bidirectional communication between clients and server?
-
Explain what "messages" in socket.io are and what we do with them.
Our Starter App - realtime-circles
Clone the realtime-circles
starter code:
-
cd ~/code
-
git clone https://git.generalassemb.ly/SEI-CC/realtime-circles.git
-
cd realtime-circles
-
npm i
-
nodemon
to start the Express backend -
Browse to
localhost:3000
Clicking creates a circle of random size and color.
Our goal is to make this a realtime multi-player circle-fest!
Let's review the starter code...
-
This is an Express app generated using
express-generator
. -
As a usual best practice in the land of the MERN-Stack,
app.js
has been renamed toserver.js
. -
Examining
server.js
reveals that much of the default middleware has been removed - no problem because we're not going to be using cookies, parsing the body for posted data, etc. -
We only have one template of interest -
index.ejs
. It results in a simpleindex.html
page that loads some simple JS and CSS. There are no other server-side routes or templates. -
Near the bottom of
index.ejs
, we are loading our app's JavaScript file,app.js
... -
Reviewing
app.js
reveals that we are using vanilla JavaScript for DOM manipulation. Yay - no jQuery! -
const circles
references a<section>
that fills most of the rendered page. -
There's a click event listener on the
circles
element. This is where the action starts.
Setting up socket.io
Both the client and server need to be configured with socket.io
Configure the Server
- To use socket.io, we first need to install its module:
npm install socket.io
-
No, the dot in the name is not a typo, it's legit.
-
We're going to be writing some server-side code pertaining to using socket.io. Should we put this new code in our server.js file, or is there a better practice?
-
We don't want to unnecessarily clutter server.js, so we're going to put our socket.io related code in a separate module file.
-
Let's create a file named
io.js
in our project's root folder:
touch io.js
-
socket.io, needs to "attach" to the http server, not the Express app.
-
In an Express app scaffolded using
express-generator
, the http server lives inside of the/bin/www
file, so that is where we will require our newio.js
module and attach to the http server:
// inside bin/www
var server = http.createServer(app);
// load and attach socket.io to http server
var io = require('../io');
io.attach(server);
- Now we need to put some code in our
io.js
module. For now let's put some test code in it to make sure things are loading correctly:
// io.js
var io = require('socket.io')();
// Listen for new connections from clients (socket)
io.on('connection', function(socket) {
console.log('Client connected to socket.io!');
});
// io represents socket.io on the server - let's export it
module.exports = io;
-
Check that
nodemon
is running our app without errors. -
No errors? Congrats the server is configured - time to configure the client!
Configure the Client
-
It takes quite a bit of JavaScript in the browser to connect to socket.io on the server and implement all of its goodness.
-
Lucky for us, the socket.io module on the server helps us out by creating a secret route that returns dynamically generated JavaScript for the client - hassle free!
-
The code returned to the browser is pre-configured with the server's info, etc.
-
All we need to do is load this special client configuration script in our
index.ejs
:
...
// special route created by socket.io on the server
<script src="/socket.io/socket.io.js"></script>
<script src="/javascripts/app.js"></script>
</body>
-
Be sure to load it before
app.js
. -
Refresh the browser and make sure there are no errors in the console.
-
The
socket.io.js
client script exposes anio
global function that we call to obtain our connection to the server. -
Let's call it and assign the returned connection object to a variable named
socket
.
// get our connection to the socket.io server
const socket = io();
console.log(socket);
...
Verify that you are we still error free.
Congrats, the client and server have both been configured
Test the Configuration
Refresh the browser and verify that:
-
The
socket
object logged in the browser's console has aconnected: true
property. -
The server's terminal window logged out the message
"Client connected to socket.io!".
Displaying Circles in Realtime
Our Realtime Requirements
We are going to code along to transform the app into a realtime multi-player circle-fest that:
-
Displays circles created by all players in realtime.
-
Clears all circles from all connected browsers when the
clear
button is clicked (a practice exercise).
Code Logic - Server
To accomplish our requirements, this is what we will need to do on the server:
-
Listen for
add-circle
messages being sent from the clients. -
When an
add-circle
message is received, forward (emit
) it (along with the data received with the message) to all connected clients (including the client that sent the message to begin with).
Code Logic - Client
To accomplish our requirements, this is what we will need to do on the client:
-
Listen for
add-circle
messages from the server.
Where will the message have originated from? -
When the
add-circle
message is received, it will contain a data object with the properties necessary to pass to the existingaddCircle()
function that creates circles! -
In the existing click handler, emit the
add-circle
message to the server, passing along an object containing theinitials
,x
,y
,dia
andrgba
properties.
Messages - Review
The add-circle
message is a custom event message that we "defined" based upon what made sense for this application.
❓ How many custom event messages can we define?.
As already noted, each message can be emitted with data. The data can be any type except for a function. Objects and arrays come in handy for sending complex rather than a single piece of primitive data.
Displaying Circles - Server Code
- This code for io.js will accomplish the goal for our code logic on the server:
io.on('connection', function(socket) {
//new code below
socket.on('add-circle', function(data) {
io.emit('add-circle', data);
});
});
-
Remember,
io
represents the server andsocket
the current client. -
With that code in place:
- When a client (
socket
) connects to the server, we're using theon
method to set up a listener on the server to listen to messages sent from that client. - When the server receives an
add-circle
message from the client, the callback function will send the same message to all clients using the server's (io
)emit
method.
- When a client (
Displaying Circles - Client Code
- Listen for an
add-circle
message from the server inapp.js
:
const socket = io();
// listen to the server for the `add-circle` event
socket.on('add-circle', function(data) {
console.log(data);
});
-
Here on the client (browser), we have the
socket
object representing our realtime connection to the server. -
For now, we're simply logging out data received from the server - baby steps!
-
Now let's update the click event listener to emit an
add-circle
message to the server with the data:
circles.addEventListener('click', function(evt) {
// replace current line of code with this code
socket.emit('add-circle', {
initials: initials,
x: evt.clientX,
y: evt.clientY,
dia: randomBetween(10,100),
rgba: getRandomRGBA()
});
});
- ❓ Our goal is for this message to be received by ________?
Displaying Circles - Messaging Check
Let's open two browsers on localhost:3000
and make sure the consoles show the messages as we click!
Cool! Let's continue coding the client...
Next, let's refactor addCircle()
so that we can just pass in the data object received with the message:
// was -> function addCircle(x, y, dia, rgba) {
// updated to include initials and take advantage of destructuring assignment
function addCircle({x, y, dia, rgba, initials}) {
...
}
-
Using ES2015's Destructuring Assignment, we can pass in an object as an argument and that object's properties will be assigned to the listed variables.
-
Note also that an
initials
variable has been added to hold the user's initials that initiated the message. -
All that's left is to call the
addCircle()
function from oursocket.on
listener insideapp.js
:
// listen to the server for the `add-circle` event
socket.on('add-circle', function(data) {
// console.log(data);
addCircle(data);
});
- Use two browsers with different initials and take it for a test drive!
Now that we have the circles displaying in realtime, let's turn our attention to the next item on the roadmap - clearing the display
Clear All Circles Practice (10 mins)
-
Let's get into breakout rooms and make the
clear
button clear all connected user's displays instead of just yours. -
Hints: This will require a new event message in addition to the
add-circle
event message.
Deploy to Heroku
Set aside your fears and:
- Create a local git repo:
git init
- Add all files:
git add -A
- Commit:
git commit -m "Initial commit"
- Make sure you are logged in to Heroku:
heroku login
- Create a Heroku deployment:
heroku create
- Deploy your repo to Heroku:
git push heroku main
- Once deployed, open the app:
heroku open
Realtime Is Fun! 👏
❓ Questions
-
What is the name of the method used to send messages from the server/client to the client/server?
-
What method is used to set up a listener for a message?
-
What are the names of the event messages available to us?
More Big Fun? Track Players
-
In the realm of realtime, tracking connected users or players is known as tracking presence.
-
It would be nice to know who's connected to our
realtime-circles
app, so let's do this!
Track Players - Server Code Logic
-
When a client connects, set up a listener for a
register-player
message from that client. The client will send their initials as data with the message. -
When a client emits the
register-player
message, the server will:
(a) Add the player'ssocket.id
and initials to aplayers
object variable.
(b) Then we will then emit anupdate-player-list
message, along with the updated list of initials, as an array, to all clients. -
When a client disconnects, we will remove the player from the
players
object and again, emit theupdate-player-list
message.
Track Players - Client Code Logic
-
After the player has entered their initials, emit the
register-player
message, sending the initials as data. -
Listen for the
update-player-list
message and update the DOM by writing<li>
tags (one for each player in the array) inside of the provided<ul>
.
Tracking Players - Server Code
- Define the
players
object to hold player's initials inio.js
:
const io = require('socket.io')();
// object to hold player's initials as keys
const players = {};
- Set up the listener for the
register-player
message in which we will take care of business:
io.on('connection', function(socket) {
// new code below
socket.on('register-player', function(initials) {
// each socket has a unique id
players[socket.id] = initials;
io.emit('update-player-list', Object.values(players));
});
... existing code below
Note that
Object.values()
is from ES2016/ES7
- Set up the listener for when the player disconnects. Add this along with the other listeners:
socket.on('disconnect', function() {
delete players[socket.id];
io.emit('update-player-list', Object.values(players));
});
... existing code below
Tracking Players - Client Code
- After the player has entered their initials, emit the
register-player
message, sending the initials as data. Inapp.js
:
...
do {
initials = getInitials();
} while (initials.length < 2 || initials.length > 3);
// new code below
socket.emit('register-player', initials);
...
- Let's cache the players <ul> element into a
const
:
...
const circles = document.getElementById('circles');
// players <ul> element in the footer
const players = document.getElementById('players');
...
- Add the listener for the
update-player-list
event:
...
// listen for when the player list has changed
socket.on('update-player-list', function(data) {
players.innerHTML = data.map(player => `<li>${player}</li>`).join('');
});
...
Tracking Players - Try it out
Further Study
-
What we've created in this lesson is a single "channel" that all connected clients participate in.
-
However, it is common to want separate channels dedicated to sub-groups of clients.
-
socket.io has two options for this use case: rooms & namespaces.
-
If interested, here's a link to get you started: socket.io Rooms & Namespaces