Let's build a simple chat app using Socket.IO with chat rooms. Here's the live site link Simple Chat. Don't judge the UI π . I know it is not good and it is not even responsive. For now, let's focus on the concepts and functionality.
Before diving into the code, let's cover some concepts about WebSockets and Socket.IO.
WebSockets
WebSockets is a protocol that provides full-duplex communication, meaning it allows a connection between the client and the server, enabling real-time, two-way communication.
Socket IO
Socket.IO is a JavaScript library that simplifies the use of WebSockets. It provides an abstraction over WebSockets, making real-time communication easier to implement.
Let's dive into the coding part. Make sure Node.js is installed on your computer.
Folder Structure
server/
βββ node_modules/
βββ public/
β βββ chat.html
β βββ createRoom.html
β βββ index.html
β βββ JoinRoom.html
β βββ script.js
β βββ styles.css
βββ .env
βββ .gitignore
βββ package-lock.json
βββ package.json
βββ server.js
Initialize the Project
npm init -y
install the required packages, first, let's install the express and nodemon
npm install express nodemon
let's configure the nodemon in package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"dev":"nodemon server.js"
},
let's set up our application, and create a file name server.js
or index.js
as you wish.
const express = require("express");
const env = require("dotenv");
const app = express();
const port = process.env.PORT || 3000;
const { createServer } = require("node:http");
const server = createServer(app);
//when the user enters the localhost:3000, the request is handled by this route handler
app.get('/', (req, res) => { //get request
res.send('<h1>Hello world</h1>');
});
server.listen(port, () => { //listen on the port
console.log(`server started ${port}`);
});
socket.io requires direct access to the underlying HTTP server to establish WebSocket connections. so we require the createServer
from the HTTP module. and listen on the port.
npm run dev //start the server
we are not going to use any frontend frameworks, so let's serve the HTML static file from the server.Β
create a folder public
and create an index.html
inside the public
folder
const path = require("path"); //require the path module
app.use(express.static("public")); //make the folder as static
//in the app.get let's send the html
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "public", "index.html"));
});
server.listen(port, () => { //listen on the port
console.log(`server started ${port}`);
});
Now if you open the localhost, you can see the HTML page.
let's integrate socket io
npm install socket.io
const { Server } = require('socket.io'); //require
const io = new Server(server); //initialize the new instance of the socket.io by passing the server object
//listen on the connection event for incoming sockets
io.on('connection', (socket) => {
console.log('user connected');
});
Now in the index.html
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io(); //establish a connection
</script>
</body>
the above script is used to enable real-time communication between the client and the server.
when you open localhost you can see the user connected
as the output.
Emit some events
The main idea of socket.io is that you can send and receive any data you want. JSON and Binary data are also supported.
Let's make it so that when the user sends the message, the server gets it as a chat message event.Β
in the index.html
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const form = document.getElementById('form');
const input = document.getElementById('input');
form.addEventListener('submit', (e) => {
e.preventDefault();
if (input.value) {
socket.emit('message', input.value);
input.value = '';
}
});
</script>
and print out the message received from the client.
io.on('connection', (socket) => {
socket.on('message', (msg) => {
console.log('message: ' + msg);
});
});
Broadcasting
Now we have received the message from the client, then we need to broadcast it to all the users.
socket.io gives the io.emit()
method, to emit the events or messages.
io.on('connection', (socket) => {
socket.on('message', (msg) => {
io.emit('message', msg);
});
});
So, now the messages from the client are broadcasted to all the users, we need to capture the message and include it in the page.
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const form = document.getElementById('form');
const input = document.getElementById('input');
const messages = document.getElementById('messages');
form.addEventListener('submit', (e) => {
e.preventDefault();
if (input.value) {
socket.emit('message', input.value);
input.value = '';
}
});
socket.on('message', (msg) => {
const item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
});
</script>
the output image is shown below.
We have done the basic chat application, let's make chat rooms.
How do chat rooms work?
socket io provides functionality for the rooms, so the event is emitted, and only the room members can see the message.
Now I am going to create a 3 html file in the public
folder. chat.html
Β , createRoom.html
Β , joinRoom.html
I am going to shift the code from the index.html
to the chat.html
.
In the index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index</title>
</head>
<body>
<div class="main">
<div class="text">
<h1>Chat</h1>
</div>
<button id="create-room">Create Room</button>
<button id="join-room">Join Room</button>
</div>
<script>
const createRoom = document.getElementById("create-room");
const joinRoom = document.getElementById("join-room");
createRoom.addEventListener("click", () => {
location.href = "./createRoom.html";
});
joinRoom.addEventListener("click", () => {
location.href = "./JoinRoom.html";
});
</script>
</body>
</html>
In the createRoom.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Create Room</title>
</head>
<body>
<div class="form-container">
<h1>Create Room</h1>
<form action="/create" method="post">
<input type="text" placeholder="Enter the room name" name="roomName" required>
<input type="submit" value="Create Room">
</form>
</div>
</body>
</html>
In the JoinRoom.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Join Room</title>
</head>
<body>
<div class="form-container">
<h1>Join Room</h1>
<form action="/joinRoom" method="post">
<input type="text" placeholder="Enter the room name" name="Join" required>
<input type="submit" value="Join Room">
</form>
</div>
</body>
</html>
So let me explain what I have done in this file, in the index.html I have made a UI with two buttons to create a room or join a room, it will be redirected to the respective files when it is clicked.
Creating the room
In createRoom.html
, there's a form with the POST method, and the action is set to /create
. When submitted, the request is sent to the server.
<form action="/create" method="post">
<input type="text" placeholder="Enter the room name" name="roomName" required>
<input type="submit" value="Create Room">
</form>
in server.js
app.use(express.urlencoded({ extended: true })); //parse the data
const rooms = []; //to store the rooms
app.post("/create", (req, res) => {
const roomName = req.body.roomName; //get the room name from the body
if (rooms.includes(roomName)) { //see if the room already exists
res.send("room already exits");
} else if (roomName) {
rooms.push(roomName); //push the room name to the array
res.redirect(`/chat.html?room=${roomName}`); //redirect to the chat.html with the room name
} else {
res.redirect("/createRoom.html");
}
});
we are not using any databases to store the room names, for this tutorial let's use an array to store the room names.
let's do the similar functionalities for the join room
in server.js
app.post("/joinRoom", (req, res) => {
const roomName = req.body.Join;
if (rooms.includes(roomName)) {
res.redirect(`/chat.html?room=${roomName}`);
} else {
res.send("enter the valid room");
}
});
Let's make a separate JavaScript file script.js
in the public. I have copied all the JS from the chat.html to a separate file.
script.js
const socket = io();
function getUrlname(name) {
const urlPara = new URLSearchParams(window.location.search);
return urlPara.get(name);
}
const roomName = getUrlname("room");
if (roomName) {
socket.emit("join room", { roomName });
} else {
console.log("room is not valid");
}
const form = document.getElementById("form");
const input = document.getElementById("input");
const messages = document.getElementById("messages");
form.addEventListener("submit", (e) => {
e.preventDefault();
if (input.value) {
socket.emit("message", { roomName, message: input.value });
input.value = "";
}
});
socket.on("message", ({ message }) => {
if (!message) {
alert("room is not valid");
} else {
const item = document.createElement("li");
console.log(message);
item.textContent = message;
messages.appendChild(item);
}
});
// Connection state recovery
const disconnectBtn = document.getElementById("disconnect-btn");
disconnectBtn.addEventListener("click", (e) => {
e.preventDefault();
if (socket.connected) {
disconnectBtn.innerText = "Connect";
socket.disconnect();
} else {
disconnectBtn.innerText = "Disconnect";
socket.connect();
}
});
In the server, after the create room or join room is handled it is redirected to the chat.html page, we have also sent the room name in the search params.Β
In the above code, we are extracting the room name from the search params using URLSearchParams
and checking if the room is valid.
Each message from the client is emitted with the message and with the room name. With the help of this we can now check from which room the message is from.
in server.js
io.on("connection", (socket) => {
socket.on("join room", ({ roomName }) => {
if (rooms.includes(roomName)) {
socket.join(roomName);
}
});
socket.on("message", ({ roomName, message }) => {
if (rooms.includes(roomName)) {
io.to(roomName).emit("message", { message });
} else {
socket.emit("message", { message: false });
}
});
});
socket provides a join
method to join the room, and we check where the message is from, and the message is broadcasted to only that room member. this is done by the io.to(roomName).emit("message",{message})
Now open multiple tabs on the browser create different rooms join in that room and check if this works.
here's the full code for server.js
const express = require("express");
const app = express();
const env = require("dotenv");
const path = require("path");
const { Server } = require("socket.io");
const { createServer } = require("node:http");
const server = createServer(app);
env.config();
const port = process.env.PORT;
app.use(express.static("public"));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const io = new Server(server, {
connectionStateRecovery: {},
});
const rooms = [];
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "public", "index.html"));
});
app.post("/create", (req, res) => {
const roomName = req.body.roomName;
if (rooms.includes(roomName)) {
res.send("room already exits");
} else if (roomName) {
rooms.push(roomName);
res.redirect(`/chat.html?room=${roomName}`);
} else {
res.redirect("/createRoom.html");
}
});
app.post("/joinRoom", (req, res) => {
const roomName = req.body.Join;
if (rooms.includes(roomName)) {
res.redirect(`/chat.html?room=${roomName}`);
} else {
res.send("enter the valid room");
}
});
io.on("connection", (socket) => {
socket.on("join room", ({ roomName }) => {
if (rooms.includes(roomName)) {
socket.join(roomName);
}
});
socket.on("message", ({ roomName, message }) => {
if (rooms.includes(roomName)) {
io.to(roomName).emit("message", { message })
} else {
socket.emit("message", { message: false });
}
});
});
server.listen(port, () => {
console.log(`server started ${port}`);
});
we have done it!! and finally, let me tell one concept that is called connection state recovery.
In the server code, there is one line
const io = new Server(server, {
connectionStateRecovery: {},
});
and in the client script.js
// Connection state recovery
const disconnectBtn = document.getElementById("disconnect-btn");
disconnectBtn.addEventListener("click", (e) => {
e.preventDefault();
if (socket.connected) {
disconnectBtn.innerText = "Connect";
socket.disconnect();
} else {
disconnectBtn.innerText = "Disconnect";
socket.connect();
}
});
it is used to handle the disconnections by pretending that there was no disconnection.
this feature will temporarily store all the events that are sent by the server and will try to restore the state when the client reconnects.
This is it!!!
here's the link for the source code - GitHub
here's the live site link - Simple Chat
Thank You!!!
Top comments (0)