Welcome folks today in this blog post we will be building a realtime chat app with different rooms using socket.io in node.js and express using javascript. All the full source code of the application is shown below.
Get Started
In order to get started you need to initialize a new node.js
project on the command line as shown below
mkdir chatapp
cd chatapp
First of all we will initialize a package.json file for our project as shown below
npm init -y
npm i express
npm i socket.io
npm i moment
So as you can see these are the dependencies that will be required for this project.First of all we will need express for building the web server and also we will need to the socket.io library for adding the realtime chat and rooms. And then moment library is used for generating the dates and time for the messages.
One more dev dependency we will need for the project. Just install it using the below command
npm i --save-dev nodemon
This library nodemon will automatically restart your application whenever you make any kind of changes in the app.
Directory Structure of App
Now guys see the resultant directory structure of the App which contains the below files and folders
As you can see we have the public directory which contains all the static files and images and then we have got the index.js
file which contains all the server side code which will be written in node.js. And then we also have the utils folder which contains all the methods which is necessary for doing some important tasks.
After installing all these libraries now just make an index.js
file inside the root directory and copy paste the below code. This will be the starting point for the application.
index.js
Initializing a simple Socket.io & Express Server
Now first of all guys we will be starting out a simple express.js server and passing socket.io to it in the given block of code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
const path = require("path"); const http = require("http"); const express = require("express"); const socketio = require("socket.io"); const app = express(); const server = http.createServer(app); const io = socketio(server); // Set static folder app.use(express.static(path.join(__dirname, "public"))); // Run when client connects io.on("connection", (socket) => { }); const PORT = 3000; server.listen(PORT, () => console.log(`Server running on port ${PORT}`)); |
As you can see we have imported all the required libraries which is express & socket.io. And also we have imported the built in http server package to create a simple http server and then we are passing the express app as an argument to it. And then we are starting the http server at port number 3000. And then we are listening on that port number. And for initializing the socket.io library we are passing the created http server variable or reference to the socket.io constructor. And this reference is stored inside the io variable. And then we are listening for various events one such event is when the socket connects socket here means any user connects in the browser. Then we will emit events to the client and receive events from the client in this function. As you know socket.io is bidirectional library. We can talk directly with client using this protocol. And also one more thing we are doing we are making the public directory as static. Because in public directory we will be storing all the html,css and javascript files.
So first of all make a public directory inside the root directory to store all the static files. Now after this you need to make the index.html
file inside the public folder
public/index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css" integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk=" crossorigin="anonymous" /> <link rel="stylesheet" href="css/style.css" /> <title>ChatCord App</title> </head> <body> <div class="join-container"> <header class="join-header"> <h1><i class="fas fa-smile"></i> ChatCord</h1> </header> <main class="join-main"> <form action="chat.html"> <div class="form-control"> <label for="username">Username</label> <input type="text" name="username" id="username" placeholder="Enter username..." required /> </div> <div class="form-control"> <label for="room">Room</label> <input type="text" name="room" id="room" placeholder="Enter Room Name..." required> </div> <button type="submit" class="btn">Join Chat</button> </form> </main> </div> </body> </html> |
As you can see we have a simple form in which we have two input fields which is taking input for the username of the user and also the room name for the chat. And we have a simple button to enter into the chat. If you open this app inside the browser it will look as shown below
Styling the App
Now guys we will adding some styling to this boring app by applying some custom css. For this you need to create the css folder inside the public folder and inside it create a style.css
file and copy paste the below code
public/css/style.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap'); :root { --dark-color-a: #667aff; --dark-color-b: #7386ff; --light-color: #e6e9ff; --success-color: #5cb85c; --error-color: #d9534f; } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Roboto', sans-serif; font-size: 16px; background: var(--light-color); margin: 20px; } ul { list-style: none; } a { text-decoration: none; } .btn { cursor: pointer; padding: 5px 15px; background: var(--light-color); color: var(--dark-color-a); border: 0; font-size: 17px; } /* Chat Page */ .chat-container { max-width: 1100px; background: #fff; margin: 30px auto; overflow: hidden; } .chat-header { background: var(--dark-color-a); color: #fff; border-top-left-radius: 5px; border-top-right-radius: 5px; padding: 15px; display: flex; align-items: center; justify-content: space-between; } .chat-main { display: grid; grid-template-columns: 1fr 3fr; } .chat-sidebar { background: var(--dark-color-b); color: #fff; padding: 20px 20px 60px; overflow-y: scroll; } .chat-sidebar h2 { font-size: 20px; background: rgba(0, 0, 0, 0.1); padding: 10px; margin-bottom: 20px; } .chat-sidebar h3 { margin-bottom: 15px; } .chat-sidebar ul li { padding: 10px 0; } .chat-messages { padding: 30px; max-height: 500px; overflow-y: scroll; } .chat-messages .message { padding: 10px; margin-bottom: 15px; background-color: var(--light-color); border-radius: 5px; overflow-wrap: break-word; } .chat-messages .message .meta { font-size: 15px; font-weight: bold; color: var(--dark-color-b); opacity: 0.7; margin-bottom: 7px; } .chat-messages .message .meta span { color: #777; } .chat-form-container { padding: 20px 30px; background-color: var(--dark-color-a); } .chat-form-container form { display: flex; } .chat-form-container input[type='text'] { font-size: 16px; padding: 5px; height: 40px; flex: 1; } /* Join Page */ .join-container { max-width: 500px; margin: 80px auto; color: #fff; } .join-header { text-align: center; padding: 20px; background: var(--dark-color-a); border-top-left-radius: 5px; border-top-right-radius: 5px; } .join-main { padding: 30px 40px; background: var(--dark-color-b); } .join-main p { margin-bottom: 20px; } .join-main .form-control { margin-bottom: 20px; } .join-main label { display: block; margin-bottom: 5px; } .join-main input[type='text'] { font-size: 16px; padding: 5px; height: 40px; width: 100%; } .join-main select { font-size: 16px; padding: 5px; height: 40px; width: 100%; } .join-main .btn { margin-top: 20px; width: 100%; } @media (max-width: 700px) { .chat-main { display: block; } .chat-sidebar { display: none; } } |
Now if you open the app it will look something like this
Creating & Joining Rooms in Socket.io
Now guys we will be creating the dynamic rooms in socket.io with the room name given by the user. And we will be joining it. For this we need to get the value of the query parameters in the url. As we have the form when we submit it the default method is get. So all the parameter values are accessible in the urlbar. Now we want to parse and access those username and room values. For this we will be using the special library called QS parser library which stands for querystring. Now you need to include it’s cdn in the browser as shown above
1 |
https://cdnjs.cloudflare.com/ajax/libs/qs/6.11.0/qs.min.js |
For including this you need to create an another html file inside the public folder which will be chat.html
which will be the actual chat screen where users will be able to see the room name, different users name who are chatting. Now copy paste the below html code
public/chat.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css" integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk=" crossorigin="anonymous" /> <link rel="stylesheet" href="css/style.css"> <title>ChatCord App</title> </head> <body> <div class="chat-container"> <header class="chat-header"> <h1><i class="fas fa-smile"></i> ChatCord</h1> <a href="index.html" class="btn">Leave Room</a> </header> <main class="chat-main"> <div class="chat-sidebar"> <h3><i class="fas fa-comments"></i> Room Name:</h3> <h2 id="room-name">JavaScript</h2> <h3><i class="fas fa-users"></i> Users</h3> <ul id="users"> <li>Brad</li> <li>John</li> <li>Mary</li> <li>Paul</li> <li>Mike</li> </ul> </div> <div class="chat-messages"> <div class="message"> <p class="meta">Brad <span>9:12pm</span></p> <p class="text"> Lorem ipsum dolor sit amet consectetur adipisicing elit. Eligendi, repudiandae. </p> </div> <div class="message"> <p class="meta">Mary <span>9:15pm</span></p> <p class="text"> Lorem ipsum dolor sit amet consectetur adipisicing elit. Eligendi, repudiandae. </p> </div> </div> </main> <div class="chat-form-container"> <form id="chat-form"> <input id="msg" type="text" placeholder="Enter Message" required autocomplete="off" /> <button class="btn"><i class="fas fa-paper-plane"></i> Send</button> </form> </div> </div> <script src="js/main.js"></script> </body> </html> |
As you can see we are using hard coded values for the different room names and also the users also. We will replace with dynamic data coming from the server later on. Also at the bottom we are also including the query string parser library cdn and also we are including the main.js
file which will be the actual javascript file we will be writing in the next step. If you open this file it will look something like this as shown below
As you can see we have different sections which is showing everything room name, different users which are present in the room and a text field to enter the message and a button to send the message and the div section to show the messages and also a scrollbar which will automatically scroll when you type messages to the bottom.
Writing the Javascript Code
Now we will be writing the javascript
code for that you need to create a js
folder inside the public directory and create main.js
file and copy paste the below code
public/js/main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const chatForm = document.getElementById('chat-form'); const chatMessages = document.querySelector('.chat-messages'); const roomName = document.getElementById('room-name'); const userList = document.getElementById('users'); // Get username and room from URL const { username, room } = Qs.parse(location.search, { ignoreQueryPrefix: true, }); const socket = io(); // Join chatroom socket.emit('joinRoom', { username, room }); |
As you see guys at the top we are getting the references to all the elements of the DOM using Id and classes. And then we are extracting the room name and the username from the address url using the querystring library. And inside that we are passing the option ignorequeryprefix to true this means that we are neglecting the special characters such as %.& characters. We only want the username and the room value as text. And we are extracting those values and after that we are initiating a new socket.io connection and storing it inside io variable and then guys we are emitting this room name and username as an event called joinRoom and in the second argument we are passing this information in the form of an object to the express.js server which contain these two properties.
Receiving Events in Express
Now we will be receiving the event which is sent from the client side of joinRoom. So for that we need to write the below code
1 2 3 4 5 6 7 |
io.on("connection", (socket) => { socket.on("joinRoom", ({ username, room }) => { }); } |
We will receive that event like this. We will use the socket object and the on() method to receive the same event is passed also in the second argument we will have the information of roomName and username. Now we need to store these details. For this we will not be using database instead we will be storing locally using arrays. For this you need to create a separate folder called utils
which contain all the methods. Create a users.js file inside it as shown below.
utils/users.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
const users = []; // Join user to chat function userJoin(id, username, room) { const user = { id, username, room }; users.push(user); return user; } // Get current user function getCurrentUser(id) { return users.find(user => user.id === id); } // User leaves chat function userLeave(id) { const index = users.findIndex(user => user.id === id); if (index !== -1) { return users.splice(index, 1)[0]; } } // Get room users function getRoomUsers(room) { return users.filter(user => user.room === room); } module.exports = { userJoin, getCurrentUser, userLeave, getRoomUsers }; |
As you can see we are declaring an empty array of users at the very top and then we are declaring various crud operations methods. Which will delete user from the array get the number of users. Get the specific user information. And also adding the new user inside an array. Inside the object we will be storing three information which will be id,username and roomname. Id will be used a unique key to delete records and to get specific information about a user. Lastly we are exporting these methods from the file.
And similarly guys you need to create a second file which is called messages.js
which will hold the single method to format the message using moment. Here we will be showing the date and time.
utils/messages.js
1 2 3 4 5 6 7 8 9 10 11 |
const moment = require('moment'); function formatMessage(username, text) { return { username, text, time: moment().format('h:mm a') }; } module.exports = formatMessage; |
As you can see we are exporting the message using the username and the actual message and then we are using moment.format() to get the current time in am or pm. Lastly we are exporting this method also.
Now we will be using these methods inside the index.js
file like this as shown below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
const formatMessage = require("./utils/messages"); const { userJoin, getCurrentUser, userLeave, getRoomUsers, } = require("./utils/users"); io.on("connection", (socket) => { socket.on("joinRoom", ({ username, room }) => { const user = userJoin(socket.id, username, room); socket.join(user.room); // Welcome current user socket.emit("message", formatMessage(botName, "Welcome to ChatCord!")); // Broadcast when a user connects socket.broadcast .to(user.room) .emit( "message", formatMessage(botName, `${user.username} has joined the chat`) ); // Send users and room info io.to(user.room).emit("roomUsers", { room: user.room, users: getRoomUsers(user.room), }); }); }); |
As you can see we are welcoming the user with the a message sent by the admin or the bot. Here you can change the botname to any name of your choice using the constant variable botName. And then we are showing the roomName and also updating the users which are added in the room in the sidebar section as shown below. As you can see we are emitting the event roomUsers back to the client passing the roomName and the actual username. Now we need to receive it inside the client side. Now we need to go back to main.js
file and copy paste the below code
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
let botName = "ChatCord Bot" socket.on('roomUsers', ({ room, users }) => { outputRoomName(room); outputUsers(users); }); // Add room name to DOM function outputRoomName(room) { roomName.innerText = room; } // Add users to DOM function outputUsers(users) { userList.innerHTML = ''; users.forEach((user) => { const li = document.createElement('li'); li.innerText = user.username; userList.appendChild(li); }); } |
As you can see we are adding the roomName and the numbers of users to the DOM using some simple vanilla javascript. We are dynamically creating the DOM elements and then using the forEach loop to loop through all the users. If you open the app and type the username and roomName and open chat.html it will look something like this
As you can see we got the welcome message by the bot. And also showing the roomName and the users in the sidebar. And also you see the time of the message also well formatted. And also for this message that you see for this we need to write code inside the main.js
file to receive this event that the server is sending which is message event
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
socket.on('message', (message) => { console.log(message); outputMessage(message); // Scroll down chatMessages.scrollTop = chatMessages.scrollHeight; }); // Output message to DOM function outputMessage(message) { const div = document.createElement('div'); div.classList.add('message'); const p = document.createElement('p'); p.classList.add('meta'); p.innerText = message.username; p.innerHTML += `<span> ${message.time}</span>`; div.appendChild(p); const para = document.createElement('p'); para.classList.add('text'); para.innerText = message.text; div.appendChild(para); document.querySelector('.chat-messages').appendChild(div); } |
As you can see we are receiving this event and then we are getting the actual message object which contains the message and also the username and also the time as well. We are justing add it inside the DOM as shown above.
Sending Chat Messages & Displaying It in DOM
And now we will handle the situation when the user present in the room types the message in the chat box and click the send button. Now we need to submit this event to the server with the message as shown below
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
chatForm.addEventListener('submit', (e) => { e.preventDefault(); // Get message text let msg = e.target.elements.msg.value; msg = msg.trim(); if (!msg) { return false; } // Emit message to server socket.emit('chatMessage', msg); // Clear input e.target.elements.msg.value = ''; e.target.elements.msg.focus(); }); |
As you can see we have binded a submit event handler to the form. We are taking the message and sending a event to the server which is called chatMessage to the server alongside with the message. And also we are resetting the input field and also focusing on it.
Now at the server side you need to catch this event as shown below
index.js
1 2 3 4 5 6 7 8 9 |
io.on("connection", (socket) => { socket.on("chatMessage", (msg) => { const user = getCurrentUser(socket.id); io.to(user.room).emit("message", formatMessage(user.username, msg)); }); }); |
As you can see we are once again using the same methods to format the message with time. This time we are finding out which user has sent the message. For that we are using the getCurrentUser() and passing the socket’s id to it. And then we are emitting the message to all the users in the room the message that is sent as shown below
Handling Users Leaving Chat Room
Now we will be handling the situation when the user presses the leave room
button and they exit out of the room. How to remove them from the users array and from the screen. For this first of all we will be binding a onclick event Listener on the button in main.js as shown below
main.js
1 2 3 4 5 6 7 8 |
//Prompt the user before leave chat room document.getElementById('leave-btn').addEventListener('click', () => { const leaveRoom = confirm('Are you sure you want to leave the chatroom?'); if (leaveRoom) { window.location = '../index.html'; } else { } }); |
As you can see when users press this button first of all we are asking the users in an alert box do they want to leave and when they press yes in alert box we are redirecting them to the index.html home page. Now when they exit out of chat page. Automatically event will be triggered of socket disconnect inside index.js. We need to handle that event by writing the below code inside index.js file as shown below
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
io.on("connection", (socket) => { // Runs when client disconnects socket.on("disconnect", () => { const user = userLeave(socket.id); if (user) { io.to(user.room).emit( "message", formatMessage(botName, `${user.username} has left the chat`) ); // Send users and room info io.to(user.room).emit("roomUsers", { room: user.room, users: getRoomUsers(user.room), }); } }); }); |
As you can see we have the disconnect event inside that we are using the userLeave() to remove the user from the array. Here we are passing the id of the socket. And then we will be updating the users as well on the screen. For example we need to remove the name of the user from the chat screen once the user lefts the chat.
Full Source Code
Wrapping it up we will be providing the full source code of main.js
file as shown below
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
const path = require("path"); const http = require("http"); const express = require("express"); const socketio = require("socket.io"); const formatMessage = require("./utils/messages"); const { userJoin, getCurrentUser, userLeave, getRoomUsers, } = require("./utils/users"); const app = express(); const server = http.createServer(app); const io = socketio(server); // Set static folder app.use(express.static(path.join(__dirname, "public"))); const botName = "ChatCord Bot"; // Run when client connects io.on("connection", (socket) => { socket.on("joinRoom", ({ username, room }) => { const user = userJoin(socket.id, username, room); socket.join(user.room); // Welcome current user socket.emit("message", formatMessage(botName, "Welcome to ChatCord!")); // Broadcast when a user connects socket.broadcast .to(user.room) .emit( "message", formatMessage(botName, `${user.username} has joined the chat`) ); // Send users and room info io.to(user.room).emit("roomUsers", { room: user.room, users: getRoomUsers(user.room), }); }); // Listen for chatMessage socket.on("chatMessage", (msg) => { const user = getCurrentUser(socket.id); io.to(user.room).emit("message", formatMessage(user.username, msg)); }); // Runs when client disconnects socket.on("disconnect", () => { const user = userLeave(socket.id); if (user) { io.to(user.room).emit( "message", formatMessage(botName, `${user.username} has left the chat`) ); // Send users and room info io.to(user.room).emit("roomUsers", { room: user.room, users: getRoomUsers(user.room), }); } }); }); const PORT = 3000; server.listen(PORT, () => console.log(`Server running on port ${PORT}`)); |
GITHUB REPOSITORY