DEV Community

Mohamed A. Othman
Mohamed A. Othman

Posted on

An Introduction to Socket.IO

Introduction

Socket.io is a powerful tool for creating real-time applications with two-way communication between the server-side and the client-side in a syntax that looks as if you are just triggering and listening to events. It leverages the power of WebSockets along with several fallbacks. It can be used to create bidirectional interactions, such as real-time dashboards, chat applications, and multiplayer games.

Socket.io is an open-source library created by Guillermo Rauch. It is built with Engine.IO, which is a lower-level abstraction on top of WebSocket technology. The WebSocket API protocol was standardized in 2011. It is a Transmission Control Protocol (TCP) that only relies on HTTP for its initial handshake. After the handshake is complete, the connection is left open so that the server and the client can pass messages back and forth as needed.

It also provides the ability to seamlessly use other real-time protocols if WebSockets are not available. For example, it will fall back on JSON long polling in the absence of WebSocket support.

Long polling is essentially a trick to emulate the WebSocket behavior in browsers that don't support WebSockets.

After a long polling request is made, it is held onto by the server instead of immediately responding as a traditional HTTP request would, When data becomes available, the long polling request will typically be made.

Creating HTTP server with Socket.IO

To get Socket.io running, we need to have a lease one client and one server set up to talk to each other.

Getting server and client-ready

To create a Node.js HTTP server with Socket.IO, follow these steps:

1- Make sure Express, and Socket.IO are installed to use them, By running this command in the terminal npm i -s express socket.io , Express, and Socket.IO will be installed and ready to use.

2- Create a new file called server.js , This will be your server-side code:

// Importing necessary libraries to create the server.
const express = require('express');
const app = express();
const http = require('http');
const socketIO = require('socket.io');

// Adding express application route middleware to serve the index.html page.
app.get('/', function (req, res) {
        res.sendFile(__dirname + '/index.html');
});

// Creating the http server using HTTP module provided by Node.js
const server = http.Server(app);

// Listening to the server we created on port 5000.
server.listen(5000);

// Create socket.io instance be passing the server we created to the socket library
const io = socketIO(server);

// Listening to socket connection event to handle communication state after the handshake communication was established.
io.on('connection', function (socket) {
        socket.emit('greeting-from-server', { greeting: 'Hello Client' });
        socket.on('greeting-from-client', function (message) { console.log(message); });
});

3- you may see the server.js will read a file called index.html. You'll need to create this as well, as shown in the following code:

<!DOCTYPE html>
<html>
        <head></head>
        <body>
                <script src="/socket.io/socket.io.js"></script>
                <script>
                        var socket = io('http://localhost:5000');
                        socket.on('greeting-from-server', function (message) {
                                document.body.appendChild(document.createTextNode(message.greeting));
                                socket.emit('greeting-from-client', {
                                        greeting: 'Hello Server'
                                });
                        });
                </script>
        </body>
</html>

4- With those two files in place, and starting the server by type node server in the terminal from the same server.js file directory, This will start a new Node server on port 5000 , if you navigate to [http://localhost:5000](http://localhost:5000) , you should see a message that says "Hello Client" in the browser.

5- You should also see a message in the terminal with an object that contains a message that say "Hello Server".

What exactly happens

Express is a collection of HTTP utilities and middleware that make it easier to use Node as a web server. Although Express provides a robust API that isn't available out of the box from the built-in Node HTTP Module.

We created a new Express server instance with const app = express(); . We passed this to the http.server(); method. By passing our Express app as the first argument to the HTTP server, we told Node that we wanted to use Express as our handler for HTTP requests.

The browser displays a message that originated on the server, whereas the server displays a message that originated on the client. These messages are both relayed by Socket.IO.

The io.on('connection', function(socket) {...}); method in the server-side code listens for any new client-side socket connections. When the client loads a page with Socket.IO on the client-side, a new connection will be created here.

When the server gets a new socket connection, it will emit a message to every available socket that says "Hello Client". when the client gets this message, it will render it to the DOM. It also emits a message of its own that the server will listen for.

Creating Real-Time application & MongoDB

Although there is a great deal of power in bidirectional communication, Socket.IO is also a perfect tool for creating unidirectional real-time experiences. Many applications need to have some sort of dashboard interface to display analytical data or to show to the state of the application data. For an application state that frequently changes, or if multiple users are changing the state, creating the dashboard in real-time makes for a much better experience.

We can send static data to our client as we can send dynamic data too, by using MongoDB as a NoSQL database that stores data as JSON format documents.

To send dynamic data from a MongoDB database via Socket.IO, we can add a new step when we created our server by connecting to the MongoDB database.

// Importing mongoose from mongoose library
const mongoose = require("mongoose");

// Connecting to mongodb database named [dashboard]
mongoose.connect("mongodb://localhost:27017/dashboard");

// Calling on open, and on error functions to see database connection state.
mongoose.connection
                .once("open", function() { console.log("Connected to MongoDB database."); })
                .on("error", function(err) { console.log("MongoDB connection error: " + err); });

After we created the MongoDB connection we can use mongoose.js instance to create our database model and run CRUD operations query.

// 1- Creating Mongodb database Model
// Importing mongoose from mongoose library
const mongoose = require("mongoose");

// Creating data model using mongoose provided API for creating new Schema.
const CommentSchema = mongoose.Schema({
        user: String,
        comment: String
});

// Creating the Comment model by mongoose provided model function, by passing the table name as a first argument, and created schema as a second argument.
const Comment = mongoose.model("Comment", CommentSchema);

.
.
.

// 2- Quering MongoDB collections.
// getting all comment documents from MongoDB.
const comments = await Comment.find({});

// send ready data to the client using socket connection.
socket.emit('comment.add', comments);

Having Two-Way conversations

Although we can perform some interesting things with one-way communication, the real power of Socket.IO begins to show when the client and server are both participants in a constant dialog.

creating a simple chat room

A basic chat room application is one of the most widely used demos, This shows off Socket.IO or even web sockets in a more general sense.

Building a basic chat room with Socket.IO is neither difficult or complex. This is the sort of application that Socket.IO was designed for.

To create a simple chat application with Socket.IO, follow these steps:

1- Create a server.js file, this file will start the server and emit Socket.IO events whenever a new message is posted to the chat application, In addition to the typical Socket.IO server setup, we will need to add the following code:

// Listening to socket connection event to handle communication state after the handshake communication was established.
io.on("connection", function(socket) {
        // Listen to "message.send" event
        socket.on("message.send", function(data) {
                // Emit to "message.sent" event after "message.send" event fires.
                io.emit("message.sent", data);
        });
})

2- Now, create your index.html template, This will include a <form id="message-form"></form> at the top of the page to post new messages, It also contains a <div class="messages"></div> container to hold our chat messages.

<div class="container">
        <form id="message-form">
                <p>
                        <label>Username</label>
                        <input class="form-control" id="username" />
                </p>
                <p>
                        <label>Message</label>
                        <textarea class="form-control" id="message"></textarea>
                </p>
                <button class="btnbtn-primary" type="submit">Send</button>
        </form>
        <div id="messages"></div>
</div>

3- Then, add the client-side JavaScript. This will submit messages to the server and render messages when they are emitted from the server, as shown in the following code:

// Update the users count
socket.on('message.sent', function (data) {
        $('#messages').prepend(`
            <div>
                 <hr />
                 <div><strong>${data.username}</strong></div>
                 <p>${data.message}</p>
            </div>
     `);
});
$(function () {
        $('#message-form').on('submit', function (e) {
                e.preventDefault();
                socket.emit('message.send', {
                        message: $('#message').val(),
                        username: $('#username').val()
                });
        });
});

The server-side code will act as a hub for incoming messages. When new messages come, it will emit them to all connected sockets.

We will submit the messages from our form on the client-side, we will also render new messages when they are emitted from the server. in this way, the client who emits the message will listen for the same message.sent the event, as all the other clients.

Socket.IO life cycle

If the server maintains a list of our connected sockets, it should always be mindful of when a socket disconnects.

A socket can disconnect for any number of those reasons:

  • The user may navigate away from the web page that the WebSocket connection is on.
  • The user's internet may go down.

When these things happen, we can tap into the disconnect event to notify the client-side that the socket is no longer available.

We can implement adding or removing online user based on their socket connection, To remove or add user's references follow these steps:

1- First listen for the socket disconnect event on the server-side, when this occurs, we will emit an event back to the clients with the ID of the socket that was disconnected.

Socket.IO associates every socket with a unique ID, ****which we can use to manage our sockets.

io.on("connection", function(socket) {
        io.emit("user.add", socket.id);
        socket.on("disconnect", function() {
                io.emit("user.remove", socket.id);  
        });
});

2- Then add an element to the view that contains the list of users:

<div id="users"></div>

3- Finally, in the client, we will listen to the user.add and user.remove events to add or remove users as they are connected or disconnected:

socket.on("user.add", function(id) {
        $("#users").prepend(`<p id="${id}">${id}</p>`)
});

socket.on("user.remove", function(id) {
        $(`#${id}`).remove();
})

Although the ID of a socket is primarily available for internal use, when we manage a list of connected users, it can be beneficial to have a record of the socket IDs to associate it with the rendered list in our interface.

In this case, we will use the socket ID as the actual id attribute for our DOM elements. The ID will look similar to a random combination of numbers and letters, Ex: AL8r1DvmiQVT50trAAAC. By using the socket ID in sync with the socket life cycle events, we will be able to show the list of currently active users on the page.

What about private messages...?

Sometimes, you need to send a private message to just one other socket and not every socket that may be listening in. As the server-side is in charge of managing all the connected sockets, we can specify the sockets that our events are emitted to granularity.

In this example, I will use this ability to emit specific sockets to create a simple app that specifies which used to give a hug to. Only the giver and receiver will be aware that the hug was initiated.

1- Add the relevant events to your server. These will be in charge of managing a list of connected users and emitting private messages to users (as required). In addition to the typical Socket.IO server-side setup. you will requiring the following code:

// We will keep a record of all connected sockets
var sockets = {};
io.on('connection', function (socket) {
        // Emit the connected users when a new socket connects
        for (vari in sockets) {
                socket.emit('user.add', {
                        username: sockets[i].username,
                        id: sockets[i].id
                });
        }
        // Add a new user
        socket.on('username.create', function (data) {
                socket.username = data;
                sockets[socket.id] = socket;
                io.emit('user.add', {
                        username: socket.username,
                        id: socket.id
                });
        });
        // Send the hug event to only the socket specified
        socket.on('user.hug', function (id) {
                sockets[id].emit('user.hugged', socket.username);
        });
        // Remove disconnected users
        socket.on('disconnect', function () {
                delete sockets[socket.id];
                io.emit('user.remove', socket.id);
        });
});

2- Now, create a index.html template to display the interface for your application:

<div class="container">
        <h1><span class="glyphiconglyphicon-heart"></span> Hugs!</h1>
        <hr />
        <form id="add-username" class="row">
                <div class="col-md-3">
                        <input class="form-control" id="username" placeholder="Username" />
                </div>
                <div class="col-md-3">
                        <button class="btnbtn-primary">Join</button>
                </div>
        </form>
        <div class="row">
                <div class="col-md-6" id="sockets" style="display:none"></div>
                <div class="col-md-6" id="hugs"></div>
        </div>
</div>

3- Then, add listeners to the client side in order to display the users. Also, keep a log of the private messages that have been emitted with the following code:

function assUser(user) {
        $("#sockets").append(`
                <div id="${user.id}" class="socket">
                        <span class="glyphicon-user"></span>
                        ${user.username} - <a href="#" class="hug" data-username="${user.username}" data-id="${user.id}">Hug</a>
                        <hr />
                </div>
        `);
}

function addUsername (e) {
        e.preventDefault();
        socket.emit("username.create", $("#username").val());
        $("#sockets").show();
        $(this).hide();
}

function giveHug(e) {
        var id = $(this).data('id');
        var username = $(this).data('username');

        e.preventDefault();

        socket.emit('user.hug', id);

        $('#hugs').prepend(`
                 <p>
                         <span class="glyphiconglyphicon-heartempty"></span>
                         You just hugged ${username}.
                         <hr />
                 </p>
        `);
}

socket.on('users.list', function (list) {
        list.forEach(addUser);
});

socket.on('user.hugged', function (username) {
        $('#hugs').prepend(`
                <p>
                        <span class="glyphiconglyphicon-heart"></span>
                        ${username} just hugged you.
                        <hr />
                </p>
        `);
});

socket.on('user.remove', function (id) {
        $('#' + id).remove();
});

socket.on('user.add', addUser);

$(function () {
        $('#sockets').delegate('.hug', 'click', giveHug);
        $('#add-username').on('submit', addUsername);
});

By maintaining a list of available sockets in our server-side object, we will be able to search for any socket by its ID, when we have the socket that we want to send a private message to, we can emit an event to only this socket and no others.

Please ❤ and Share

Thanks for reading this! Hopefully, these few points make the Socket.IO explanation a less confusing experience. If not, I’m happy to clear up any questions (or correct any errors you find here).

Top comments (2)

Collapse
 
abdellani profile image
Mohamed ABDELLANI

Great article, thanks for sharing :)

Collapse
 
ishan0445 profile image
Ishan Rayeen

Nice tutorial. Thanks for sharing😊👍