Skip to content

Instantly share code, notes, and snippets.

@jim-clark
Last active October 17, 2020 06:22
Show Gist options
  • Save jim-clark/e9571e16e19a8b0e9504 to your computer and use it in GitHub Desktop.
Save jim-clark/e9571e16e19a8b0e9504 to your computer and use it in GitHub Desktop.

This presentation is available here.


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


Intro to socket.io


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.


Intro to socket.io


  • 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...


Intro to socket.io


  • 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?


Intro to socket.io


  • 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?...


Intro to socket.io


  • As we learned in week one, it was the demand for modern web applications that led to the development of:


Intro to socket.io


  • 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.


Intro to socket.io


  • 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.


Intro to socket.io

Basic Architecture

  • socket.io Clients & Servers send "messages" to each other...

  • and both Clients & Servers can listen and react to those "messages".


Intro to socket.io

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.


Intro to socket.io

What are "messages"? (cont.)


  • 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'
     }

Intro to socket.io

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" are and what we do with them in socket.io.


Our Starter App
realtime-circles


Our Starter Apprealtime-circles

  • Because you might deploy this app, please copy the realtime-circles starter code to your working directory.

  • $ npm install

  • $ nodemon and 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...


Review the Code for realtime-circles


  • 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 to server.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.


Review the Code for realtime-circles


  • 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...


Review the Code for realtime-circles


  • 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.


Setting up socket.io


Both the client and server
need to be configured with socket.io


Configure the Server


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?


Configure the Server (cont.)


  • 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

Configure the Server (cont.)


  • 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 new io.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);

Configure the Server (cont.)


  • 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;

Configure the Server (cont.)


  • Check that nodemon is running our app without errors.

  • No errors? Congrats the server is configured - time to configure the client!


Configuring the Client
(Browser)


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.


Configure the Client (cont.)


  • 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.


Configure the Client (cont.)


  • The socket.io.js client script exposes an io 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);
    
     ...

Congrats, the client and server
have both been configured!


But are we still error free?
Let's check...


Test the Configuration


  • Refresh the browser and verify that:

    • The socket object logged in the browser's console has a
      connected: 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:

  1. Listen for add-circle messages being sent from the clients.

  2. 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:

  1. Listen for add-circle messages from the server.
    Where will the message have originated from?

  2. When the add-circle message is received, it will contain a data object with the properties necessary to pass to the existing addCircle() function that creates circles!

  3. In the existing click handler, emit the add-circle message to the server, passing along an object containing the initials, x, y, dia and rgba 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


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 and socket the current client.


Displaying Circles - Server Code


  • With that code in place:

    • When a client (socket) connects to the server, we're using the on 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.

Displaying Circles - Client Code


Displaying Circles - Client Code


  • Listen for an add-circle message from the server in app.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 :)


Displaying Circles - Client Code (cont.)

  • 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


To recap, our code so far:

  1. Emit's add-circle messages and data to the server when a user clicks.

  2. 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!


Displaying Circles - Client Code (cont.)

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.


Displaying Circles - Client Code (cont.)


  • All that's left is to call the addCircle() function from our socket.on listener inside app.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!


Clear All Circles
Practice (10 - 15 mins)


  • 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.


Who would like to come up and share their solution?


Deploy to Heroku


Set aside your fears and:

  1. Create a local git repo: git init
  2. Add all files: git add -A
  3. Commit: git commit -m "Initial commit"
  4. Make sure you are logged in to Heroku: heroku login
  5. Create a Heroku deployment: heroku create
  6. Deploy your repo to Heroku: git push heroku master
  7. Set app to use only one web dyno: heroku ps:scale web=1
  8. 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 know 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


  1. 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.

  2. When a client emits the register-player message, the server will:
    (a) Add the player's socket.id and initials to a players object variable.
    (b) Then we will then emit an update-player-list message, along with the updated list of initials, as an array, to all clients.

  3. When a client disconnects, we will remove the player from the players object and again, emit the update-player-list message.

Track Players - Client Code Logic


  1. After the player has entered their initials, emit the register-player message, sending the initials as data.

  2. 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 in io.js:

     var io = require('socket.io')();
     
     // object to hold player's initials as keys
     var players = {};

Tracking Players - Server Code (cont.)

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


Tracking Players - Server Code (cont.)

  • 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 in app.js:

...
	
  do {
    initials = getInitials();
  } while (initials.length < 2 || initials.length > 3);
  // new code below
  socket.emit('register-player', initials);
	
...
	

Tracking Players - Client Code (cont.)


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');
	
...

Tracking Players - Client Code

  • 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!


Tracking Players - Run It!


Tracking Players - Summary


  • 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 the update-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>'s innerHTML.


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


References


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment