Socket IO with NodeJS — with a complete example

What is a WebSocket? Making a real time chat app using Socket.IO with NodeJS? How easy can that be? We shall see!

A WebSocket facilitates Real Time Bidirectional Communication between two or more clients through a connecting server. Which is a contrary to WebRTC, which allows a direct Real Time Bidirectional Communication between two or more clients without the need for a server at all times [meaning, once the server connects the clients, the clients can now communicate directly with each other.]. I am planning on writing on WebRTC next 😃

Understanding the benefits of WebSocket can be realized better when developing a chat app. First of all, sending HTTP requests over the network to facilitate an active communication between clients would be very costly. Redundant header information would need to be sent over network each and every time. While in case of WebSocket connections, once a costly initial call establishes connection between any 2 clients, now very small packages of data can be sent between each other. This can facilitate faster, lighter data passing and also continuous data passing or streams of data for audio/video etc types of information.

BEWARE PEOPLE! this article shows code snippets with a mix of Javascript ES5 and ES6 😛

How do we make a basic WebSocket app in NodeJS?

* A server app to establish communication between Clients. [NodeJS, NodeExpress, Socket.IO etc]

* Client side app for listening and casting messages to its audience. [JS, HTML, CSS etc]

Suggested:

* Nodemon (for hotloading, for dev env only, install if you don’t already have it installed globally on your machine)

bash

npm i express
npm i socket.io
npm i -D nodemon //as dev dependency, use -g if desired

The server app skeleton:

server.js

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var PORT = process.env.PORT || 5000;
//WE WILL USE THIS SERVER APP TO SERVE THE CLIENT ALSO!!
app.get('/', (req, res) => {
   res.sendFile(__dirname + '/index.html');
});
// ALL WEBSOCKET CODE GOES HERE...
http.listen(PORT, function () {
   console.log('WebSocket App started on PORT: ' + PORT);
});

As you can see, we have loaded our Socket IO library here. Let’s use it to listen to any client who wants to connect to our app on a host and given port. Any client who will open WebSocket connection using our host and port will implicitly trigger the connect event ‘connection’ and be connected.

server.js

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var PORT = process.env.PORT || 5000;
// Listening to clients getting connected and disconnected
io.sockets.on('connection', function(socket) {
    console.log("LOG: [EVENT=connection] New client connected.");
//A client left
    socket.on('disconnect', function() {
        console.log("LOG: [EVENT=disconnect] client has disconnected.");
    });
});
http.listen(PORT, function() {
    console.log('WebSocket App started on PORT: ' + PORT);
});

The Client side:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>terminal-x</title>
  </head>
  <body>
    <!-- ALL HTML GOES HERE -->
    <textarea id="outputID" row="0" readonly></textarea>
    <input id="inputID" type="text" />
    <!-- SCRIPT GOES HERE -->
    <script src="/socket.io/socket.io.js"></script>
    <script src="/client.js"></script>
  </body>
</html>

client.js:

//SOCKET CONNECTION CHATROOM
      var sock = io.connect();
      //THIS IS WHERE WE WILL EVENTUALLY ADD CODES
      //TO EMIT AND LISTEN FOR MESSAGES FOR EACH EVENT

🌹ROOMS!

For privacy and to facilitate group chats, we will use rooms. Rooms are individual channels created inside the same socket connection. A number of clients can join each channel/room and have separate group conversations.

🍓 The sample code below pretty much completes the server side app for us:

As you can see, the first event we want to listen to an event ‘connection', once clients connect to our websocket, now we are listening for an an event ‘join_room’. Every time a client wants to create a room with a new name, we create a room/channel for that. If a client wants to create a channel/room with an existing name, it is simply ignored. Duplication now allowed. A mechanism can be implemented to append a unique identifier each time clients try to open rooms/channel with same name.

client.js

room_name = "room name I want to create and subscribe";
sock.emit('join_room', room_name);

server.js

// server side code: ROOM
io.sockets.on('connection', function(socket) {
    socket.on('join_room', function(room_name) {
        socket.join(room_name);
        console.log(socket.rooms); //all rooms created so far
    });
});

Listening to rooms: From server side, we are listening to an event ‘message_to_server’, this event can be triggered from client side from any room. Then we will broadcast that message to every client in that room. Now, how do we know which room it came from? We will add that room name inside data by the client itself. As you can see in the code below

server.js

//LISTEN TO ROOMS
socket.on('message_to_server', function({
    room_name,
    from,
    msg
}) {
    //we will emit this message to all clients in given room
});

Emitting to rooms: We got this from client side, telling use, what is the room, who the user is and what the message is. We can now emit this message

server.js

//LISTEN TO ROOMS AND EMITTING TO ALL CLIENTS IN THAT ROOM
socket.on('message_to_server', function({
    room_name,
    from,
    msg
}) {
    //we will emit this message to all clients in given room
    io.in(room_name).emit('message_to_client', {
        from,
        msg
    });
});

client.js

// EMITTING MESSAGES FROM CLIENT TO ROOM
var room_name = "name of room to which we will emit"
var from = "could be username, some way to identifiy who sent it"
var msg = "the message from this client"
sock.emit('message_to_server', { room_name, from: username, msg });

The code above will be triggered by the user from client UI, using the event ‘message_to_server’, we are adding username and room name in the payload, so server side can know which room this message should be sent out to.

Here’s, the complete code with comments! let’s study the self explanatory complete code sample.

server.js

"use strict";
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var PORT = process.env.PORT || 5000;
app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
});
// server side code: ROOM
io.sockets.on('connection', function(socket) {
    console.log("LOG: [EVENT=connection] New client connected.");
//EACH TIME WE WANT TO CREATE A NEW ROOM FROM CLEINT SIDE, EVENT = CREATE
    socket.on('join_room', function(room_name) {
        console.log("LOG: [EVENT=join_room] [room_name=" + room_name + "]");
        console.log(socket.rooms);
        socket.join(room_name);
    });
//LISTEN TO ROOMS
    socket.on('message_to_server', function({
        room_name,
        from,
        msg
    }) {
//we will emit this message to all clients in given room
io.in(room_name).emit('message_to_client', {
            from,
            msg
        });
    });
//A user disconnected from room
    socket.on('disconnect', function() {
        console.log("LOG: [EVENT=disconnect] A client has disconnected.");
    });
});
http.listen(PORT, function() {
    console.log('WebSocket App started on PORT: ' + PORT);
});

Let's take a look at client side script. Most of the code is static, for easy understanding. It can be made dynamic and event triggered after basic understanding.

client.js

//SOCKET CONNECTION CHATROOM
   var sock = io.connect();
   var outputDOM = document.getElementById("outputID");
   var inputDOM = document.getElementById("inputID");
   var username = "";
   var msg = "";
   var room_name = "";
   //+++++++++++++++++++++++++++++++++++++
   // REQUEST SERVER TO CREATE A NEW ROOM:
   //+++++++++++++++++++++++++++++++++++++
   room_name = "some room name";
   sock.emit('join_room', room_name);
   //+++++++++++++++++++++++++++++++++++++
   // LISTENER TO SERVER:
   //+++++++++++++++++++++++++++++++++++++
   sock.on('message_to_client', function({
       from,
       msg
   }) {
       // ******
       outputDOM.innerHTML += "</br>" + decryptedMSG(data);
       outputDOM.rows += 1;
       // ******
   });
   //+++++++++++++++++++++++++++++++++++++
   // SEND TO SERVER:
   //+++++++++++++++++++++++++++++++++++++
   function sendToServer(msg, room_name) {
       sock.emit('message_to_server', {
           room_name,
           from: username,
           msg
       });
   }
   //USER GAVE INPUT: SEND TO SERVER
   inputDOM.addEventListener("keyup", function(event) {
       if (event.keyCode === 13) {
           event.preventDefault();
           //+++++++++++++++++++++++++++++++++++++
           if (inputDOM.value != "") {
               //SEND TO SERVER
               sendToServer(inputDOM.value, token);
               //RESET INPUT TERMINAL
               inputDOM.value = ""
           } else {
               inputDOM.placeholder = "empty message cant be sent!";
           }
           //+++++++++++++++++++++++++++++++++++++
       }
   });

If there is still any confusion please take a look at the complete project on websocket using rooms. Clone it and follow instructions in readme.md. its is not a barebone boilerplate for Socket app, but should be simple enoigh to understand.

LINK: A complete sample project written with Socket.IO in nodeJS



To view or add a comment, sign in

More articles by Navid Mostafiz

  • Delegates and Closure in Swift: Made Easy!

    Previously, I was writing about Closures in JavaScript. To my surprise, I found that Delegating a function throughout a…

  • Callbacks in JS: The Coffee Story!

    A Callback function is executed when some asynchronous function has completed its task and has returned… A. Lets take…

  • Lexical Scopes in JS: Easy as Pie!

    We have functions, blocks, variables and they have some scopes. All of them behave differently in different scopes and…

  • GenStage in Erlang/Elixir: Easy as Jelly!

    A GenStage facilitates communication between different Erlang/Elixir modules on the same or different nodes using a…

  • Gen_Servers in Erlang/Elixir: Easy as Jelly!

    So, what is a GenServer? A GenServer facilitates communication between different Erlang/Elixir modules on the same or…

  • Closures in JS: Easy as pie!

    A closure is a scope or environment that a function captures. The scope of a function is always outward.

Others also viewed

Explore content categories