Socket.IO is a library that enables real-time, bidirectional, and event-based communication between the browser and the server.
Approach: First, it is important to note that when a new socket is created, it is assigned a unique Id which is retrieved by calling socket.id. This id can be stored within a user object and we can assign an identifier such as a username. For the frontend, we will use React, and for backend node.js and express. Create two folders in your main directory name server(backend) and client(frontend). Socket.on will be an event that will be called whenever needed, and the event is called by the socket.emit, where we call the event and pass the parameter if it needs.
First we will create a backend part of the application:
Step 1: Install Dependencies for backend
npm init npm install cors npm install express npm install nodemon npm install socket.io npm install http
Project Structure: It will look like the following.
Step 2: Create Index.js. Socket.on is used for join whenever a user will join frontend will emit join event and backend will emit message event and send the message that user has joined.
There are three methods used inside index.js:
- connection: Whenever a user will join the room from the home page, connection event will be fired and everyone in the room will get the message that the user had joined, and the user is added to that room data.
- sendMessage: Now when any of the users will send the message from input in chat, sendmessage event will get fired and the message is added inside the array of messages with the username.
- disconnect: When the user leaves the chat the disconnect event will get fired and the user is removed from the array of users of that room and the message will be sent that the user had left the chat.
Filename: index.js
Javascript
const express = require( 'express' ); const socketio = require( 'socket.io' ); const http = require( 'http' ); const cors = require( 'cors' ); const { addUser, removeUser, getUser, getUsersInRoom } = require( "./users" ); const app = express(); const server = http.createServer(app); const io = socketio(server); app.use(cors()) io.on( "connection" , (socket) => { socket.on( 'join' , ({ name, room }, callback) => { const { error, user } = addUser( { id: socket.id, name, room }); if (error) return callback(error); // Emit will send message to the user // who had joined socket.emit( 'message' , { user: 'admin' , text: `${user.name}, welcome to room ${user.room}.` }); // Broadcast will send message to everyone // in the room except the joined user socket.broadcast.to(user.room) .emit( 'message' , { user: "admin" , text: `${user.name}, has joined` }); socket.join(user.room); io.to(user.room).emit( 'roomData' , { room: user.room, users: getUsersInRoom(user.room) }); callback(); }) socket.on( 'sendMessage' , (message, callback) => { const user = getUser(socket.id); io.to(user.room).emit( 'message' , { user: user.name, text: message }); io.to(user.room).emit( 'roomData' , { room: user.room, users: getUsersInRoom(user.room) }); callback(); }) socket.on( 'disconnect' , () => { const user = removeUser(socket.id); if (user) { io.to(user.room).emit( 'message' , { user: 'admin' , text: `${user.name} had left` }); } }) }) server.listen(process.env.PORT || 5000, () => console.log(`Server has started.`)); |
Step 3: Create a User.js file that contains the following functions.
These are the functions storing users, getting users, deleting users, and used in index.js file.
- addUser: This function will be called whenever a connection is made or a new user joined and the user name is stored in the array of that room and if the username is already taken by any of the users inside that room the user will not get entry to chat.
- removeUser: This function will be called whenever a connection is destroyed or a user leaves and the user name is deleted from the array of that room.
- getUser: This function takes id as a parameter and returns the username by finding it from the array.
- getUsersInRoom: This function will return the name of all the users in the room to display in chat.
filename: User.js
Javascript
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 (existingUser) { return { error: "Username is taken" }; } 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 }; |
Now we will work on the frontend part of the application:
Step 1: Install react for frontend.
npx create react-app "client"
Step 2: After react installed, install dependencies for Project inside client folder.
npm install query-string; npm install react-emoji; npm install react-router; npm install socket.io-client;
Project Structure: It will look like the following.
Step 3: Inside App.js, create routes for the pages join page and chat page and import the components for both pages to display on that route.
Filename: App.js
Javascript
import React from 'react' ; import Chat from './components/Chat/Chat' ; import Join from './components/Join/Join' ; import { BrowserRouter as Router, Route } from "react-router-dom" ; const App = () => { return ( <Router> <Route path= "/" exact component={Join} /> <Route path= "/chat" component={Chat} /> </Router> ); } export default App; |
Step 4: Inside Chat.js socket.emit is calling the join and other events, and socket.on is creating events called from backend like for storing messages, users data. This is the main component and will call all other components by sending parameters like usernames, messages to show in chat. Whenever users will access this page, join event will be called from the backend. Whenever messages or user data changes i.e. if any user joins or leaves or any user posted a message event will be called and roomdata event will be called inside useeffect to show the message of user entry or leaving and storing it in message array. Infobar will show the name of the room. Text container will show the name of all users in the room. Messages will show all messages in chat. Input will take the input, store it in an array, and fire sendmessage event to send the message with the user name.
Filename: Chat.js
Javascript
import React, { useState, useEffect } from "react" ; import queryString from "query-string" ; import io from 'socket.io-client' ; import TextContainer from '../TextContainer/TextContainer' ; import Messages from '../Messages/Messages' ; import InfoBar from '../InfoBar/InfoBar' ; import Input from '../Input/Input' ; import "./Chat.css" ; var connectionOptions = { "force new connection" : true , "reconnectionAttempts" : "Infinity" , "timeout" : 10000, "transports" : [ "websocket" ] }; const Chat = ({ location }) => { const [name, setName] = useState( '' ); const [room, setRoom] = useState( "" ); const [users, setUsers] = useState( '' ); const [message, setMessage] = useState( '' ); const [messages, setMessages] = useState([]); const ENDPOINT = 'localhost:5000' ; useEffect(() => { const { name, room } = queryString.parse(location.search); setName(name); setRoom(room); socket.emit( 'join' , { name, room }, (error) => { if (error) { alert(error); } }) return () => { socket.emit( 'disconnect' ); socket.off(); } }, [ENDPOINT, location.search]); useEffect(() => { socket.on( 'message' , (message) => { setMessages([...messages, message]); }) socket.on( "roomData" , ({ users }) => { setUsers(users); }); }, [messages, users]) //Function for Sending Message const sendMessage = (e) => { e.preventDefault(); if (message) { socket.emit( 'sendMessage' , message, () => setMessage( '' )) } } console.log(message, messages); return ( <div className= "outerContainer" > <div className= "container" > <InfoBar room={room} /> <Messages messages={messages} name={name} /> <Input message={message} setMessage={setMessage} sendMessage={sendMessage} /> </div> <TextContainer users={users} /> </div> ) }; export default Chat; |
Step 5: Create Chat.css for adding style to Chat.js.
Filename: Chat.css
CSS
.outerContainer { display : flex; justify- content : center ; align-items: center ; height : 100 vh; background-color : #1A1A1D ; } .container { display : flex; flex- direction : column; justify- content : space-between; background : #FFFFFF ; border-radius: 8px ; height : 60% ; width : 35% ; } @media ( min-width : 320px ) and ( max-width : 480px ) { .outerContainer { height : 100% ; } .container { width : 100% ; height : 100% ; } } @media ( min-width : 480px ) and ( max-width : 1200px ) { .container { width : 60% ; } } |
Step 6: Displaying the room name on the header of the page and we are getting the room name from the chat component.
Filename: Infobar.js
Javascript
import React from 'react' ; import './InfoBar.css' ; const InfoBar = ({ room }) => ( <div className= "infoBar" > <div className= "leftInnerContainer" > <h3>{room}</h3> </div> <div className= "rightInnerContainer" > <a href= "/" >Leave</a> </div> </div> ); export default InfoBar; |
Step 7: Create Infobar.css file.
Filename: Infobar.css
CSS
.infoBar { display : flex; align-items: center ; justify- content : space-between; background : #2979FF ; border-radius: 4px 4px 0 0 ; height : 60px ; width : 100% ; } .leftInnerContainer { flex: 0.5 ; display : flex; align-items: center ; margin-left : 5% ; color : white ; } .rightInnerContainer { display : flex; flex: 0.5 ; justify- content : flex-end; margin-right : 5% ; } .onlineIcon { margin-right : 5% ; } |
Step 8: The user will give the input and send the message then sendmessage event will be called from the backend and data stores in the message array.
Filename: Input.js
Javascript
import React from 'react' ; import './Input.css' ; const Input = ({ setMessage, sendMessage, message }) => ( <form className= "form" > <input className= "input" type= "text" placeholder= "Type a message..." value={message} onChange={({ target: { value } }) => setMessage(value)} onKeyPress={event => event.key === 'Enter' ? sendMessage(event) : null } /> <button className= "sendButton" onClick={e => sendMessage(e)}>Send</button> </form> ) export default Input; |
Step 9: Create Input.css file.
Filename: Input.css
CSS
.form { display : flex; border-top : 2px solid #D3D3D3 ; } .input { border : none ; border-radius: 0 ; padding : 5% ; width : 80% ; font-size : 1.2em ; } input:focus, textarea:focus, select:focus { outline : none ; } .sendButton { color : #fff !important ; text-transform : uppercase ; text-decoration : none ; background : #2979FF ; padding : 20px ; display : inline- block ; border : none ; width : 20% ; } |
Step 10: Taking input from users their name and the room to join from the home page and when the user joins to send the user to chat page.
Filename: Join.js
Javascript
import React, { useState } from "react" ; import { Link } from 'react-router-dom' ; import './Join.css' ; const Join = () => { const [name, setName] = useState( '' ); const [room, setRoom] = useState( "" ); return ( <div className= "joinOuterContainer" > <div className= "joinInnerContainer" > <h1 className= "heading" >Join</h1> <div> <input placeholder= "Name" className= "joinInput" type= "text" onChange= {(event) => setName(event.target.value)} /> </div> <div> <input placeholder= "Room" className= "joinInput mt-20" type= "text" onChange= {(event) => setRoom(event.target.value)} /> </div> <Link onClick={e => (!name || !room) ? e.preventDefault() : null } to={`/chat?name=${name}&room=${room}` }> <button className={ 'button mt-20' } type= "submit" >Sign In </button> </Link> </div> </div> ); }; export default Join; |
Step 11: Create a Join.css file to add styling to the Join.js component.
Filename: Join.css
CSS
html, body { font-family : 'Roboto' , sans-serif ; padding : 0 ; margin : 0 ; } #root { height : 100 vh; } * { box-sizing: border-box; } .joinOuterContainer { display : flex; justify- content : center ; text-align : center ; height : 100 vh; align-items: center ; background-color : #1A1A1D ; } .joinInnerContainer { width : 20% ; } .joinInput { border-radius: 0 ; padding : 15px 20px ; width : 100% ; } .heading { color : white ; font-size : 2.5em ; padding-bottom : 10px ; border-bottom : 2px solid white ; } .button { color : #fff !important ; text-transform : uppercase ; text-decoration : none ; background : #2979FF ; padding : 20px ; border-radius: 5px ; display : inline- block ; border : none ; width : 100% ; } .mt -20 { margin-top : 20px ; } @media ( min-width : 320px ) and ( max-width : 480px ) { .joinOuterContainer { height : 100% ; } .joinInnerContainer { width : 90% ; } } button:focus { outline : 0 ; } |
Step 12: Displaying the messages in the chatbox and if the messages are from the current user then it will be on the right side with different background color else it will be on the left side with the different background color.
Filename: Message.js
Javascript
import React from 'react' ; import './Message.css' ; import ReactEmoji from 'react-emoji' ; const Message = ({ message: { text, user }, name }) => { let isSentByCurrentUser = false ; const trimmedName = name.trim().toLowerCase(); if (user === trimmedName) { isSentByCurrentUser = true ; } return ( isSentByCurrentUser ? ( <div className= "messageContainer justifyEnd" > <p className= "sentText pr-10" >{trimmedName}</p> <div className= "messageBox backgroundBlue" > <p className= "messageText colorWhite" > {ReactEmoji.emojify(text)} </p> </div> </div> ) : ( <div className= "messageContainer justifyStart" > <div className= "messageBox backgroundLight" > <p className= "messageText colorDark" > {ReactEmoji.emojify(text)} </p> </div> <p className= "sentText pl-10 " >{user}</p> </div> ) ); } export default Message; |
Step 13: Message.css
CSS
.messageBox { background : #F3F3F3 ; border-radius: 20px ; padding : 5px 20px ; color : white ; display : inline- block ; max-width : 80% ; } .messageText { width : 100% ; letter-spacing : 0 ; float : left ; font-size : 1.1em ; word-wrap: break-word; } .messageText img { vertical-align : middle ; } .messageContainer { display : flex; justify- content : flex-end; padding : 0 5% ; margin-top : 3px ; } .sentText { display : flex; align-items: center ; font-family : Helvetica ; color : #828282 ; letter-spacing : 0.3px ; } .pl -10 { padding-left : 10px ; } .pr -10 { padding-right : 10px ; } .justifyStart { justify- content : flex-start; } .justifyEnd { justify- content : flex-end; } .colorWhite { color : white ; } .colorDark { color : #353535 ; } .backgroundBlue { background : #2979FF ; } .backgroundLight { background : #F3F3F3 ; } |
Step 14: Sending the message one by one from the array of messages and the name in the message component which then show it to users.
Filename: Messages.js
Javascript
import React from 'react' ; import Message from './Message/Message' ; import './Messages.css' ; const Messages = ({ messages, name }) => ( <div> {messages.map((message, i) => <div key={i}> <Message message={message} name={name} /> </div>)} </div> ); export default Messages; |
Messages.css
CSS
.messages { padding : 5% 0 ; overflow : auto ; flex: auto ; } |
Step 15: We get the array of all the users in the room and displaying it on the page by applying some css.
Filename: TextContainer.js
Javascript
import React from 'react' ; import onlineIcon from '../../icons/onlineIcon.png' ; import './TextContainer.css' ; const TextContainer = ({ users }) => ( <div className= "textContainer" > { users ? ( <div> <h1>People currently chatting:</h1> <div className= "activeContainer" > <h2> {users.map(({ name }) => ( <div key={name} className= "activeItem" > {name} <img alt= "Online Icon" src={onlineIcon} /> </div> ))} </h2> </div> </div> ) : null } </div> ); export default TextContainer; |
Filename: TextContainer.css
CSS
.textContainer { display : flex; flex- direction : column; margin-left : 100px ; color : rgb ( 201 , 25 , 25 ); height : 60% ; justify- content : space-between; } .activeContainer { display : flex; align-items: center ; margin-bottom : 50% ; } .activeItem { display : flex; align-items: center ; } .activeContainer img { padding-left : 10px ; } .textContainer h 1 { margin-bottom : 0px ; } @media ( min-width : 320px ) and ( max-width : 1200px ) { .textContainer { display : none ; } } |
Step to run backend: Go inside the backend folder and open the terminal to write the following command.
npm start
Step to run frontend: Go inside the folder and open the terminal to write the following command.
npm start
Output: Open the browser and type localhost 3000 to see the application running.