How to build chat application using Nodejs, Express and Socket.IO

Introduction

In this example we will see how to build chat application using Nodejs, express and socket.io. You can find the original demo at https://socket.io/.

This chat application has the following features:

  • Asks for nick name from participant
  • Tracks how many participants are there in the room
  • Tracks who joins the room
  • Tracks who leaves the room
  • Tracks who is typing a message
  • Displays the message with name

Prerequisites

Nodejs, Socket.IO, Express, jQuery, HTML, CSS

Create Project

Create a project root directory called nodejs_chat anywhere on the disk space.

We may not mention the project’s root directory name in the subsequent sections but we will assume that we are creating files with respect to the project’s root directory.

Now we need to install the required package or modules for the chat application. The modules required to be installed are express, socket.io and jquery.

First we need initialize the nodejs project by executing the command npm init on the project root directory using command line tool.

This will ask for several information as shown below in the image:

chat application using nodejs express and socket.io

Then it will show what content is going to be written in package.json file:

chat application using nodejs express and socket.io

Now execute the following commands to have the required things in place:

chat application using nodejs express and socket.io

Create Chat UI

Now we will create HTML file that will show the chat window where participants will type their respective messages.

Notifications, such as, participant joins, leaves, total number of participants etc. are displayed in gray color text.

The index.html file is kept under static folder.

<!doctype html>
<html>
<head>
    <title>Nodejs Chat</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
	<ul class="pages">
		<li class="chat page">
		  <div class="chat_area">
			<ul class="messages"></ul>
		  </div>
		  <input class="input_message" placeholder="Type here..."/>
		</li>
		<li class="login page">
		  <div class="form">
			<h3 class="title">What's your nickname?</h3>
			<input class="username_input" type="text" maxlength="14" />
		  </div>
		</li>
	</ul>
	<script src="//code.jquery.com/jquery-3.5.0.js"></script>
	<script src="/socket.io/socket.io.js"></script>
	<script src="/chat.js"></script>
</body>
</html>

Apply Style

We apply some basic style to the user interface (UI).

The file style.css is kept under static folder.

* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }

ul {
  list-style: none;
  word-wrap: break-word;
}

.log {
	color: gray;
}

.pages {
  height: 100%;
  /*margin: 0;
  padding: 0;*/
  width: 100%;
}

.page {
  height: 100%;
  position: absolute;
  width: 100%;
}

.login.page {
  background-color: #000;
}

.login.page .form {
  height: 100px;
  margin-top: -100px;
  position: absolute;

  text-align: center;
  top: 50%;
  width: 100%;
}

.login.page .form .username_input {
  background-color: transparent;
  border: none;
  border-bottom: 2px solid #fff;
  outline: none;
  padding-bottom: 15px;
  text-align: center;
  width: 400px;
}

.login.page .title {
  font-size: 200%;
}

.login.page .username_input {
  font-size: 200%;
  letter-spacing: 3px;
}

.login.page .title, .login.page .username_input {
  color: #fff;
  font-weight: 100;
}

.chat.page {
  display: none;
}

.input_message {
  font-size: 100%;
}

.chat_area {
  height: 100%;
  padding-bottom: 60px;
}

.messages {
  height: 100%;
  margin: 0;
  font-size: 150%;
  overflow-y: scroll;
  padding: 10px 20px 10px 20px;
}

.message.typing .message_body {
  color: gray;
}

.username {
  font-weight: 700;
  overflow: hidden;
  padding-right: 15px;
  text-align: right;
}

.input_message {
  border: 10px solid #000;
  bottom: 0;
  height: 60px;
  left: 0;
  outline: none;
  padding-left: 10px;
  position: absolute;
  right: 0;
  width: 100%;
}

Client Functionalities

We write different chat functionalities which are required to display different messages to the participants on chat window or chat room.

The function setParticipantsMessage() displays the total number of participants currently in the chat room.

log() function logs the message in gray color text on chat window.

setUsername() function displays the user name on the chat window once he/she enter the name on initial screen where it asks for nick name.

sendMessage() sends the or broadcast the message to the active participants.

addChatMessage() adds the message to the chat window. While someone is typing it shows the person is typing otherwise once typing is done it sends the message to the window.

addChatTyping() shows while someone is typing.

removeChatTyping() removes typing once someone has just finished typing and hit the enter key.

win.keydown determines the events and accordingly it works with the events.

We show message when participant leaves, joins etc.

$(function() {
	var win = $(window);
	var usernameInput = $('.username_input'); // Input for username
	var messages = $('.messages'); // Messages area
	var inputMessage = $('.input_message'); // Input message input box

	var loginPage = $('.login.page'); // The login page
	var chatPage = $('.chat.page'); // The chatroom page
	
	var username;
	var connected = false;
	var typing = false;
	var currentInput = usernameInput.focus();
	
	var socket = io();
	
	const setParticipantsMessage = (data) => {
		var message = '';
		if (data.numberOfUsers === 1) {
		  message += "There is 1 participant";
		} else {
		  message += "There are " + data.numberOfUsers + " participants";
		}
		
		log(message);
	}
	
	const log = (message, options) => {
		var el = $('<li>').addClass('log').text(message);
		addMessageElement(el, options);
	}
	
	const setUsername = () => {
		username = cleanInput(usernameInput.val().trim());

		if (username) {
		  loginPage.fadeOut();
		  chatPage.show();
		  loginPage.off('click');
		  currentInput = inputMessage.focus();

		  socket.emit('user_added', username);
		}
	}
	
	const sendMessage = () => {
		var message = cleanInput(inputMessage.val());

		if (message && connected) {
			inputMessage.val('');
			addChatMessage({
				username: username,
				message: message
			});
			socket.emit('new_message', message);
		}
	}

	const addChatMessage = (data, options) => {
		var typingMessages = getTypingMessages(data);
		
		options = options || {};
		
		if (typingMessages.length !== 0) {
			options.fade = false;
			typingMessages.remove();
		}

		var usernameDiv = $('<span class="username"/>').text(data.username).css('font-weight', 'bold');
		var messageBodyDiv = $('<span class="messageBody">').text(data.message);

		var typingClass = data.typing ? 'typing' : '';
		
		var messageDiv = $('<li class="message"/>').data('username', data.username).addClass(typingClass).append(usernameDiv, messageBodyDiv);

		addMessageElement(messageDiv, options);
	}
	
	const addChatTyping = (data) => {
		data.typing = true;
		data.message = 'is typing';
		addChatMessage(data);
	}

	const removeChatTyping = (data) => {
		getTypingMessages(data).fadeOut(function () {
			$(this).remove();
		});
	}

	const addMessageElement = (el, options) => {
		var el = $(el);

		// Setup default options
		if (!options) {
			options = {};
		}
		if (typeof options.fade === 'undefined') {
			options.fade = true;
		}
		if (typeof options.prepend === 'undefined') {
			options.prepend = false;
		}

		// Apply options
		if (options.fade) {
			el.hide().fadeIn(150);
		}
		
		if (options.prepend) {
			messages.prepend(el);
		} else {
			messages.append(el);
		}
		
		messages[0].scrollTop = messages[0].scrollHeight;
	}
	
	const cleanInput = (input) => {
		return $('<div/>').text(input).html();
	}
	
	const updateTyping = () => {
		if (connected) {
			if (!typing) {
				typing = true;
				socket.emit('typing');
			}
		}
	}

	const getTypingMessages = (data) => {
		return $('.typing.message').filter(function (i) {
			return $(this).data('username') === data.username;
		});
	}

	win.keydown(event => {
		//console.log('event.which: ' + event.which);
		// Auto-focus the current input when a key is typed
		if (!(event.ctrlKey || event.metaKey || event.altKey)) {
			currentInput.focus();
		}
		
		// When the client hits ENTER on their keyboard
		if (event.which === 13) {
			if (username) {
				sendMessage();
				socket.emit('typing_stop');
				typing = false;
			} else {
				setUsername();
			}
		}
	});

	inputMessage.on('input', () => {
		updateTyping();
	});
	
	loginPage.click(() => {
		currentInput.focus();
	});

	inputMessage.click(() => {
		inputMessage.focus();
	});

	socket.on('login', (data) => {
		connected = true;

		var message = "Welcome to Nodejs Chat Room";
		
		log(message, {
			prepend: true
		});
		
		setParticipantsMessage(data);
	});

	socket.on('new_message', (data) => {
		addChatMessage(data);
	});
	
	socket.on('user_joined', (data) => {
		log(data.username + ' joined');
		setParticipantsMessage(data);
	});

	socket.on('user_left', (data) => {
		log(data.username + ' left');
		setParticipantsMessage(data);
		removeChatTyping(data);
	});

	socket.on('typing', (data) => {
		addChatTyping(data);
	});

	socket.on('typing_stop', (data) => {
		removeChatTyping(data);
	});

	socket.on('disconnect', () => {
		log('You have been disconnected');
	});

	socket.on('reconnect', () => {
		log('You have been reconnected');
		if (username) {
			socket.emit('user_added', username);
		}
	});

	socket.on('reconnect_error', () => {
		log('Attempt to reconnect has failed');
	});
});

Server Functionalities

Now we will see how to handle chat functionalities at server side.

We implement what action has to be taken upon new participant arrival, new message arrival from a participant, while a participant is typing, while participant disconnect himself/herself from the chat room.

The below code is written into index.js page.

var path = require('path');
var express = require('express');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var port = process.env.PORT || 4000;

server.listen(port, function(){
    console.log('Listening on %d:' + port);
});

app.use(express.static(path.join(__dirname, 'static')));

var numberOfUsers = 0;

io.on('connection', (socket) => {
	var userJoined = false;
	
	socket.on('new_message', (msg) => {
		socket.broadcast.emit('new_message', {
			username: socket.username,
			message: msg
		});
	});
	
	socket.on('user_added', (username) => {
		if (userJoined) return;

		socket.username = username;

		userJoined = true;
		
		numberOfUsers++;
		
		socket.emit('login', {
			numberOfUsers: numberOfUsers
		});
		
		socket.broadcast.emit('user_joined', {
			username: socket.username,
			numberOfUsers: numberOfUsers
		});
	});

	socket.on('typing', () => {
		socket.broadcast.emit('typing', {
			username: socket.username
		});
	});
	
	socket.on('typing_stop', () => {
		socket.broadcast.emit('typing_stop', {
			username: socket.username
		});
	});

	socket.on('disconnect', () => {
		if (userJoined) {
			--numberOfUsers;
			
			socket.broadcast.emit('user_left', {
				username: socket.username,
				numberOfUsers: numberOfUsers
			});
		}
	});
});

Testing the Application

Now you can run the application by executing the command node index.js on the project root directory from command line tool.

You will see that your server is listening on port 4000. If you want you may change this server port also.

Now you can open as many chat windows as you wish.

Download

Thanks for reading.

Leave a Comment