This presentation is available here.
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.
-
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!
Within the context of 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!
-
With your pair, discuss what type of applications are made possible by realtime bidirectional communication between clients and a server.
-
Be prepared to share your thoughts in 2 minutes.
-
We just discussed the kinds of cool apps that leverage realtime communications.
-
So, what technology do we need to know as developers to incorporate realtime in our own apps...
-
So far in SEI, we've learned quite a bit about the HTTP protocol.
-
Who would like to remind us of the process of communication between client and server when using HTTP?
-
From the beginning, HTTP implemented a request / response process.
-
In HTTP, all communication is initiated by the client.
-
The web server responds once to the client's request, and the "conversation" ends until the client sends another request.
-
Obviously, HTTP is not our ticket to creating realtime apps, what is?...
-
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 communication - the websocket protocol.
-
Working with websockets natively is not terribly difficult, however, we've learned that using libraries such as jQuery 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.
-
socket.io Clients & Servers send "messages" to each other...
-
and both Clients & Servers can listen and react to those "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
'login'
message, we might send the following JS object:{ email: '[email protected]', password: 'abc123' }
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" are and what we do with them in socket.io.
-
Because you might deploy this app, please copy the
realtime-circles
starter code to your working directory. -
$ npm install
-
$ nodemon
and browse tolocalhost: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 view of interest -
index.ejs
. There will not be any full-page refreshes.
What application architecture is this known as? -
Near the bottom of
index.ejs
, we are loading our app's JavaScript file,app.js
...
-
Reviewing
app.js
reveals that we are using native JavaScript for DOM manipulation. Yay - no jQuery! -
var 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.
-
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!
-
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 var socket = io(); console.log(socket); ...
But are we still error free?
Let's check...
-
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!".
-
-
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).
-
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).
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.
-
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.
-
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 (
-
Listen for an
add-circle
message from the server inapp.js
:var 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 ________?
To recap, our code so far:
-
Emit's
add-circle
messages and data to the server when a user clicks. -
Receives
add-circle
messages emitted from the server and console logs their data.
-
Let's open two browsers on localhost:3000 and make sure our console shows the messages as we click!
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 take advantage of ES2015's 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 is 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 test drive that sucka!
Now that we have our circles displaying in realtime
let's turn our attention to the next item on the roadmap - clearing the display!
-
Partner up 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.
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 master
- Set app to use only one web dyno:
heroku ps:scale web=1
- Once deployed, open the app:
heroku open
-
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?
-
In the realm of realtime, tracking connected users or players is know as tracking presence.
-
It would be nice to know who's connected to our
realtime-circles
app, so let's do this!
- 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.
-
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>
.
-
Define the
players
object to hold player's initials inio.js
:var io = require('socket.io')(); // object to hold player's initials as keys var 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
After the player has entered their initials,
emit the register-player message, sending the initials
as data in app.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 var:
...
var circles = document.getElementById('circles');
// players <ul> element in the footer
var 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) { var playerList = '<li>' + data.join('</li><li>') + '</li>'; players.innerHTML = playerList; }); ...
-
Using the
join()
method to create a string from an array is very efficient!
-
When a player visits the page and enters their initials, the app informs the server by emitting the
register-player
message. -
The server adds the player's initials to the
players
object as a key and notifies all connected clients by emitting theupdate-player-list
message. -
Clients then receive the
update-player-list
message, generate a nice list of<li>
tags in a string, and blast that baby into the<ul>
'sinnerHTML
.
-
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