Sunday, March 6, 2022

Build a socket.io chat web app with node.js

Socket.IO is a JavaScript library for real-time web applications. It enables real-time, bi-directional communication between web clients and servers. 

This article addresses the very basic building blocks to creating a socket.io  application in Node.js. 

Source code: https://github.com/medhatelmasry/socket-chat.git

Pre-requisites

The following pre-requisites are assumed:
  • The latest versions of Node.js & npm are installed on your computer
  • You have some basic knowledge of Node.js and Express

Getting started

In a working directory, create a folder for our web app named socket-chat then change into that directory with the following commands:

mkdir socket-chat
cd socket-chat

Initialize a Node.js application with the following command:

npm init -y

Install packages ejsexpress, nodemon and  socket.io with the following command:

npm install ejs express nodemon socket.io

We need to create our server. To accomplish this, create a file in the root of your app named index.js with the the following content:

const express = require("express");
const app = express();
const server = require("http").createServer(app);
const io = require("socket.io")(server, { cors: { origin: "*" } }); 

app.set("view engine", "ejs");

app.get("/", (req, res) => {
  res.render("index", {title: "Socket.io chat example"});
}); 

const port = process.env.PORT || 3030;
server.listen(port, () => {
  console.log(`Server started and listening on port ${port}`);
});

// emit & receive messages
io.on('connection', (socket) => {
  console.log(`Someone connected with socket id: ${socket.id}`);
});

What does the above code do?

  • we initialize express, server, and io 
  • EJS is declared as our view engine
  • the / route points to a view page named index. This will be created later.
  • the server will be started on port 3030
  • whenever someone connects, the unique socket id is displayed in the server console

Edit file package.json and add the following to the "scripts" section:

"dev": "nodemon index.js"

The above allows us to run the web app using command "npm run dev". The advantage of nodemon is that it watches for any changes in the file system and restarts the server whenever a file changes.
 
Create a folder named views and add to it a file named index.ejs with the following content:

<!DOCTYPE html>
<html lang="en">
<head>
    <title><%- title %></title>
    <script src="https://cdn.socket.io/socket.io-3.0.1.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"/>
</head>
<body>
    <div class="container">
        <h3><%- title %></h3>
    </div>
</body>
</html>

Also in views/index.ejs, add the following JavaScript code below the closing </div> tag:

<script>
    const socket = io('http://localhost:3030');
    socket.on("connection", () => {});
</script>

The above code does a web socket hand-shake with the server. Start your server, if it is not started already, then point your browser to http://localhost:3030. The page looks like this:


More interesting is that your console window reveals information about socket IDs:


This proves that we have established a socket.io connection between the server and clients. Let us write some code that sends messages from one client to all the others. To do that, we will create a simple input field and a button in our frontend view. Add the following markup in views/index.ejs right under " <h3><%- title %></h3>":

<input type="text" id="msg" />
<button onclick="sendMessage()" class="btn btn-success btn-sm">
   Send Message
</button>
<hr />
<ul id="msgList" class="list-group"></ul>

In our backend index.js file,  add the following just under the console.log(. . .) statement:

socket.on('chat', (data) => {
   console.log(data);
   // broadcast the same message to all except the sender
   socket.broadcast.emit('chat', data);
});

The above code indicates that when a message named "chat" is received, it will broadcast that same message to all other clients except the sender. The variable data represents the actual message.

Now, back in our views/index.ejs frontend, we will actually emit (or send) a message. Type the following code right under "socket.on("connection", () => {});":

const sendMessage = () => {
   const msgInput = document.querySelector("#msg");
   socket.emit("chat", msgInput.value);
   document.querySelector("#msg").value = "";
}

The above is the sendMessage() handler for the button click. It sends the text entered by user to the message event named chat and, subsequently, clears the input field.

To actually see the message that is being passed around, we need to add some code to the frontend that will append each chat message received to the <ul> . . . </ul> unordered list with id msgList. Add this code below the "const sendMessage = () => { . . . . });" block: 

socket.on("chat", (data) => {
  const msg = data
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");
  var li = document.createElement("li");
  li.classList.add("list-group-item", "list-group-item-warning");
  li.textContent = msg;
  document.querySelector("#msgList").appendChild(li);
});

What does the above code do?

  • data is sanitized such that & is replaced by &amp; and < is replaced by &lt; and > is replaced by &gt;
  • an li element is created and given bootstrap styling
  • data is assigned as content to the li element
  • finally, the li item is appended to the unordered list with id msgList

Testing our chat app

Point two (or more) browser windows to http://localhost:3030. In the first window enter a message then click on "Send message". You will notice the second window will pickup the same message:






This is a very simple socket.io app. I hope you will use this starting point to do much more useful and interesting applications.

No comments:

Post a Comment