Chat App
How to build a chat app with Socket.io and Express.js?
- In this article, we'll build a simple chat application that allows users to talk to each other in real-time using Socket.io and Express.js.
- Socket.io is a Javascript library for a real-time web application. It enables real-time, bi-directional communication between web clients and servers.
- It has two components a server and a client.
- Install socket.io using the command:
npm i socket.io
Setting Up the Web Server
- The server is responsible for, serving the HTML, CSS, and JS files, starting the Socket.io connection, and receiving and broadcasting events to clients.
- Start the coding part of the server side, start with the following command
npm init
-
here is an application file structure.
- To start with we need to create a web server that serves the HTML, CSS, and JavaScript files to our clients. We'll create a file named main.js and include the following code:
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 });
// Get room and users
socket.on('roomUsers', ({ room, users }) => {
outputRoomName(room);
outputUsers(users);
});
// Message from server
socket.on('message', (message) => {
console.log(message);
outputMessage(message);
// Scroll down
chatMessages.scrollTop = chatMessages.scrollHeight;
});
// Message submit
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();
});
// 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);
}
// 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);
});
}
//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 {
}
});
- Now let's go ahead and create a frontend using HTML files: index.html and chat.html
In index.html, It is the base page of the application
<!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>
<select name="room" id="room">
<option value="Buisness">Buisness</option>
<option value="Education">Education</option>
<option value="Personal">Personal</option>
</select>
</div>
<button type="submit" class="btn">Join Chat</button>
</form>
</main>
</div>
</body>
</html>
In chat.html, It second page of the application when entering in socket room for the chat using room id.
<!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>ChatUS</title>
</head>
<body>
<div class="chat-container">
<header class="chat-header">
<h1><i class="fas fa-smile"></i> ChatUS</h1>
<a id="leave-btn" 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"></h2>
<h3><i class="fas fa-users"></i> Users</h3>
<ul id="users"></ul>
</div>
<div class="chat-messages"></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="https://cdnjs.cloudflare.com/ajax/libs/qs/6.9.2/qs.min.js"
integrity="sha256-TDxXjkAUay70ae/QJBEpGKkpVslXaHHayklIVglFRT4="
crossorigin="anonymous"
></script>
<script src="/socket.io/socket.io.js"></script>
<script src="js/main.js"></script>
</body>
</html>
In css/style.css
@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, let's move to the client part using Express.js and Socket.io. We'll create a file named server.js and include the following code:
const path = require("path");
const http = require("http");
const express = require("express");
const socketio = require("socket.io");
const formatMessage = require("./utils/messages");
const createAdapter = require("@socket.io/redis-adapter").createAdapter;
const redis = require("redis");
require("dotenv").config();
const { createClient } = redis;
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";
(async () => {
pubClient = createClient({ url: "redis://127.0.0.1:6379" });
await pubClient.connect();
subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));
})();
// Run when client connects
io.on("connection", (socket) => {
console.log(io.of("/").adapter);
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 = process.env.PORT || 3000;
server.listen(PORT, () => console.log(`Server running on port ${PORT}`));
- Now start the server by command and the server will start at port 3000: http://localhost:3000/
npm start
Enter your name and select your room and click on Join Chat.
Thus, Following is step by step tutorial to create a chat application using Express.js and Socket.io.