Build a Chat Room With Node.js and Socket.io

Usman Saleem
The Startup
Published in
5 min readDec 22, 2020
Image by EyalV via slideshare

I have been working with node.js for quite some time . I started off as a Mean stack developer and now working in Mern stack which is amazing. Every JavaScript framework is interesting and has its own taste. I like node because it’s fast due to its non-blocking and single-threaded nature. While i was learning, i came across a library for chat which is Socket.IO.

Introduction

Socket.IO is a library that enables real-time, duplex communication between client and the server.

It contains Websocket which is a protocol that provides a full-duplex and low-latency channel between the server and the browser

via socket.io/docs/v3

Implementation in Node.js:

Basic overview:

  • const socketio = require(“socket.io”) // To get the package
  • const io = socketio(server) // here sever is http-server
  • io.on(‘connect’, ()=>{}) // this is first function called when connection is made between client and server
  • socket.on(‘join’, ()=> {}) and socket.join(‘user room’) //these events for joining the room
  • socket.emit(‘name of event’, ()=>{}) // it emits the event which both client or server will listen to
  • socket.on(‘name of event’,()=>{}) this is used both in client side and server side to listen to the event which is emitted by socket.emit and run the code inside it
  • socket.on(‘disconnect’,()=>{}) // it will tell when the user has disconnected

Server code integration (node):

create and cd SampleApp npm init // to setup npm packagenpm i --save express // i am using express.jsnpm i --save socket.io // for socket.io

After everything is installed. Create a file name server.js and started writing the code

server.js

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);
const {
addUser,
removeUser,
getUser,
getUsersInRoom
} = require('./utils/User');
io.on('connect', socket => {
socket.on('join', ({ username, room }, callback) => {
const { error, user } = addUser({ id: socket.id, name: username, room }); // add user with socket id and room info

if (error) return callback(error);

socket.join(user.room);

socket.emit('message', {
user: 'adminX',
text: `${user.name.toUpperCase()}, Welcome to ${user.room} room.`
});
socket.broadcast.to(user.room).emit('message', {
user: 'adminX',
text: `${user.name.toUpperCase()} has joined!`
});

io.to(user.room).emit('roomData', {
room: user.room,
users: getUsersInRoom(user.room) // get user data based on user's room
});

callback();
});
socket.on('sendMessage', (message, callback) => {
const user = getUser(socket.id);

io.to(user.room).emit('message', { user: user.name, text: message });

callback();
});
socket.on('disconnect', () => {
const user = removeUser(socket.id);

if (user) {
io.to(user.room).emit('message', {
user: 'adminX',
text: `${user.name.toUpperCase()} has left.`
});
io.to(user.room).emit('roomData', {
room: user.room,
users: getUsersInRoom(user.room)
});
}
});
server.listen(process.env.PORT || 3000, () =>
console.log('Server is running')
);

after establishing the connection and user joins the room. There will be a socket.emit event which will be listened by socket.on
socket.on(‘sendMessage’) event will be listened which contains user name and message. After this connection will be disconnected when user leaves the chat and an event will be emitted.

Create a user.js file for user crud

users.js

//Array of users
const users = [];

const addUser = ({ id, name, room }) => {
name = name.trim().toLowerCase();
room = room.trim().toLowerCase();

const existingUser = users.find(
user => user.room === room && user.name === name
);

if (!name || !room) return { error: 'Username and room are required.' };
if (existingUser) return { error: 'Username already exists.' };

const user = { id, name, room };

users.push(user);

return { user };
};

const removeUser = id => {
const index = users.findIndex(user => user.id === id);

if (index !== -1) return users.splice(index, 1)[0];
};

const getUser = id => users.find(user => user.id === id);

const getUsersInRoom = room => users.filter(user => user.room === room);

module.exports = { addUser, removeUser, getUser, getUsersInRoom };

Same events will be listened at client as well. I am using React for front-end.

Client code integration (React):

create and cd ClientSamplenpm i --save socket.io-client // for socket.io client

After installation. Create a file name clientSocket.js and add code like

import { useState, useEffect, useRef } from 'react';
import socketIOClient from 'socket.io-client';

const ENDPOINT = 'http://localhost:3000/';

const ClientSocket = ({ username, room }) => {
const socketRef = useRef();
const [users, setUsers] = useState([]);
const [messages, setMessages] = useState([]);
const [error, setError] = useState('');

useEffect(() => {
socketRef.current = socketIOClient(ENDPOINT);

socketRef.current.emit('join', { username, room }, error => {
if (error) {
setError(error);
}
});

return () => {
socketRef.current.emit('disconnect');
};
}, [username, room]);

useEffect(() => {
socketRef.current.on('message', message => {
setMessages(messages => [...messages, message]);
});

socketRef.current.on('roomData', ({ users }) => {
setUsers(users);
});
}, []);

const sendMessage = message => {
socketRef.current.emit('sendMessage', message, () => {});
};

return { users, messages, sendMessage, error };
};

export default ClientSocket;

It will use same events which is used by server to emit or listen every time user gets a message. After this, create Chat.js file

const Chat = () => {
const history = useHistory();
const { username, room } = useParams(); //these are added when user registers
const [message, setMessage] = useState(''); const { users, messages, sendMessage} = ClientSocket({
username,
room
});
const onSubmitHandler = event => {
event.preventDefault();
if (message) sendMessage(message);
setMessage('');
};
const renderActiveUsers = () => {
return users.map(user => {
return (
<p key={user.id} >
<i className="fa fa-circle"></i> {user.name}
</p>
);
});
};
return ( <div className="container-fluid d-flex">
<div className="col-lg-6">
<ul>
<h6>Messages</h6>
{messages.text}
</ul>
<form onSubmit={onSubmitHandler}>
<input
autoComplete="off"
value={message}
onChange={e => setMessage(e.target.value)}
type="text"
name="message"
/>
<button type="submit"> Send </button
</form>
</div>
<div className="col-lg-6 border-left ml-2">
{renderActiveUsers()}
</div>
</div>
)};
export default Chat;

Conclusion

Above code is simple implementation of how we setup socket.IO in both client and server. It’s an awesome and easy to integrate library.

I wrote very minimal code to let you know about the socket.io. If you want to improve it you can:

  • Write separate react(jsx) files to give it a chat app feel and apply styling.
  • Add online/offline labels

Thank you for reading .Hope you like it. :)

Reference

--

--

Usman Saleem
The Startup

Software Engineer | Full Stack Developer | Angular | React.js | Node.js | AWS