Create an Open Group Chat Using ExpressJS (NodeJs), Socket.io, ReactJs, and Tailwind CSS
What you will learn
- Socket.io
- CORS
- React useEffect, dependencies, and Cleanup.
- React useRef
- Tailwind CSS
- Optional Chaining in JavaScript
Introduction
One of the best ways to learn is by building. In this article, we will look at how to build an open group chat application using the technologies below:
- NodeJs: A JavaScript runtime built on Google’s Chrome V8 engine that is used to build network applications. Read more about it https://nodejs.org/en/about/
- ReactJs: A framework for building single-page applications with interactive user interfaces. To know more, visit this link https://reactjs.org/
- Socket.io: This is a library that allows real-time, bi-directional, and event-based communication between a client(s) and a server. Read more here: https://socket.io/
- Tailwind CSS: A CSS utility framework that can be used for designing front-end applications. Visit https://tailwindcss.com/ to know more about this framework.
How Socket.io Works
Socket.IO allows servers to communicate back to clients or browsers. Traditionally, clients only establish one-way communication with servers. This is carried out by the client sending an HTTP request, such as a GET request. For instance, the client doesn’t know how many other clients are connected to the server, can not communicate with other clients, etcetera.
NOTE: Socket.io is not a Web Socket
- We make connections between clients and a server.
- Events are emitted and fired using socket.io. When we want to emit an event, we use the
emit
method that will take the name of the event, i.e., emittedsocket.emit('eventName')
. And when we want to fire or execute an event, we use theon
method that will take the name of the event we emitted, for examplesocket.on(‘eventName')
. There is also thebroadcast
method available for every connected socket. It allows the client to message all other clients. This is useful when you want other clients to know you are typing. To know more, please visit https://socket.io/. See the image below:
Building Our Project
Step 1: Setting Up The Folder Structure
We basically need two folders. One is for our server, and the other is for our frontend application.
NOTE: make sure you have
NodeJs
installed on your computer. Visit here to install NodeJs runtime.
mkdir server
We will create the second folder, the frontend folder, using create-react-app
, as we will see soon.
Step 2: Creating our server
A server is needed; hence, we created an express.js server. This will handle any event fired from the clients. And as well, as fire or emit any event to the clients. For this, we first cd into the server folder in your terminal.
cd server
We will need to install express
, cors
and socket.io
. And to install these packages, we need to run:
npm install --save express cors socket.io
Next, create a file called app.js
and paste the following code:
- Line 1–3: We require Express, create our HTTP server, and require Cors.
- Line 6: We require socket.io and instantiate it as
io
. This is installed using Then we configure it by allowing CORS for every origin using the asterisk sign “*”. This means that any client from any origin like “http://yourigin.com”, or “http://yourorigin.com” can still connect to the server. A particular origin can be specified by replacing the asterisk “*” with the origin in question. - Lines 9 and 10: The port for which the server will run is set up.
- Line 13: We make the server enable cross-origin resource sharing. With this, any client from any domain can connect.
- Line 16: We create the “connection” event, which is fired when any client is connected to the server. It takes a parameter that represents the client that is connected. In this case, the client is referred to as the socket. And in turn, this socket contains some properties such as
id
that are unique to the client or socket.
NOTE: All other events can only be fired if a client is connected using the
connection
event.
- Line 18: we emit the event
activeUsers
which allows us to get the number of clients connected. - Line 21: we emit the
getId
event which will get the id of a client when it first connects, then we pass the socket id of the socket or client connected. - Line 24: we fire the
chat
event which will be emitted by any client when a user sends a chat. It takes as a parameter theid
andchat
of the user that sent the chat, it then emits thesendChat
event. - Line 29: the event
userNameChange
is created. This event is fired when a user changes their username. This event in turn emits to all other clients theresetChat
event. - Line 34: the
userTyping
event is created which gets fired when a user is typing. And in turn, it emits the eventsomeoneTyping
to other clients. - Line 39: we fire the
activeUsers
event which gets the number of connected users. It uses the socket.ioengine.clientsCount
method to achieve this. And then we emit the result to the clients using thecountUsers
event. - Line 40: This is the
disconnect
event that is fired whenever a client is disconnected. In it we allowed any disconnected socket to immediately emit thedisconnectNotification
event to other clients. And then it emits to other clients the number of connected users.
In order to start our server, we need to edit the “start” script in the package.json of our server folder.
{..."scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node app.js",
...},
...}
Now to start our server, we need to run
npm start
Step 2: Creating our Frontend Application
For our frontend application, we will be using ReactJs and Tailwind CSS. For installation, see React installation and Tailwind installation in React.
First, we need to cd out of our server folder in the terminal:
cd ..
Then run the commands below to install React
npx create-react-app frontend
cd frontend
The command above will install react and will allow you to cd into the react application folder. Inside the folder in your terminal, run the following command to install Tailwind CSS.
npm install -D tailwindcss postcss autoprefixer
Once that is done, run the command below to initialize tailwind:
tailwindcss init -p
The command above will create a Tailwind configuration file. Now open the file and edit the content
field to the following:
...
content: ["./src/**/*.{js,jsx,ts,tsx}"],
...
Finally, go to the index.css
file of your react application folder and paste the following:
@tailwind base;
@tailwind components;
@tailwind utilities;
Now this will allow us to use Tailwind CSS in our project.
Next, open App.js and paste in the following code:
- Lines 1–3: we import
React
,useState
useRef
anduseEffect
. - Line 4: we import
io
as our client engine. Here is the guide to importingsocket.io
on a client. - Line 5: we import customized CSS.
- Line 9: we create the
toastRef
using theuseRef
React hook which will handle the element on theDOM
that will be responsible for toasts. - Lines 12–20: with the
useState
hook, we create variables such asactiveUsers
which represents the number of connected devices.socket
which represents an instance of a client or socket that is connected.id
which represents theid
of a user.socketid
which represents the id of a client or socket.username
which indicates the username of a user.chat
which represents the chat sent by a user.allChats
which is an array that holds all the chats by all users.notification
which is used to set the notification that will be displayed by the toast.someoneTyping
which will hold the username or id of the socket of the user or client typing. - Line 23: we create the
scrollToBottom()
function, which will scroll to the bottom of the page whenever a user sends a chat. - Line 30: we create the
showToast()
function which displays the toast for any notification. It takes a background color and implements asetTimeOut()
function that will be triggered after three seconds to hide the toast. - Line 39: we create the
changeUsername()
function which will set a user’s username. And this will also map through all the chats and change the user’s username to the username entered. And finally, it emits theusernameChange
event. - Line 49: we create the
handleChatInput()
method which will take what the user has typed in and set the value tochat
. - Line 54: with the
submitChat()
function, we first check if the client is disconnected using thesocket.disconnected
property of the socket. If true, then a notification is sent only to the client that they are disconnected. And if there is no chat from the user, nothing will be submitted. If true, then the eventchat
is emitted which takes the id of the user, their chat, and their socket id assocketid
. And then finally, when the chat is submitted, thescrollToBottom()
function is called. - Line 67: we create the
logOut()
function which logs out or disconnects a user. It checks if the user is already disconnected or not. Then shows a toast with the corresponding notification. - Line 79: with the
getTime()
function we are able to get the time at which any chat will be sent. - Line 86: we create the
searchUser()
function which loops through all the chats and finds a particular user by using their id. - Line 97: we create the
handleUserTyping()
method to handle when a user is typing. we create atimeout
function that runs after every second. It emits theuserTyping
event and is been cleared using theclearTimeout()
function. - Line 107: we create our first
useEffect()
hook. This will run only once, hence there is nothing in its dependency array. We did this so that we can create only one socket instance and then add the event listeners only once as well. This will prevent issues such as a client sending two chats at a time. - Line 109: we initialize a new socket instance and pass our server endpoint to it. This is so that it can make a connection with the server.
- Line 110: we save the socket to a state.
- Line 113: we invoke the inbuilt
connect_error
event handler ofsocket.io
. This checks to see if there is a connection error and notifies the user correspondingly. - Line 120: the
activeUsers
event is emitted. - Line 123: we create the handler for the
getId
event which gets the id of the socket and saves it to the stateid
. - Line 129: we create the event handler for
countUsers
which will get the number of clients connected and set the value to the stateactiveUsers
by using the functionsetActiveUsers()
. - Line 135: the handler for the event
sendChat
is created and it gets theallChats
state array and adds the new chat to it. And finally, it calls thescrolltoBottom()
function. - Line 143: here we create the handler for the
resetChat
event. It modifies theallChats
state by changing the username of a user. - Line 150: now we create the handler for the
message
event. It triggers when themessage
event is emitted and notifies every socket with the message using thesetNotification()
function and theshowToast()
function. - Line 156: we add a clean-up function to our first
useEffect
hook. This is so as to avoid side effects such as sockets still being connected when they should be closed. - Line 154: we create another
useEffect
hook. This is because we want to be able to re-render our component when the stateallChats
updates and also be able to perform some functions based on the update. It can’t be possible in the first one because it will only run once when the component first renders. - Line 166: as the component re-render we still want to get the number of active users.
NOTE: the use of
?
on thesocket
instance. This is called optional chaining in Javascript. it is used to check if a particular object or its property is null or undefined and prevents any error associated with it.
Because on the first render, socket
is null for the second useEffect
hook, we used it so as to prevent the error of: Uncaught TypeError: Cannot read properties of null
that will be thrown.
- Line 169: we create the handler for the
disconnectNotification
event which searches for the disconnected user by using their socket id and invoking thesearchUser()
function which we have already discussed earlier. And thus it notifies all clients of a disconnected user. - Line 177: the handler for the
someoneTyping
event is created. It takes the id of the socket along with the type parameter. It checks if the user is typing or has stopped. - Line 193: we display the total number of active users using the
activeUsers
state. - Line 195: we implement the
logOut()
function inside a button. - Line 200: we display the
id
of the user. - Line 208: we implement an
Onchange
event listener to the input which takes the username we want to set. It sets theusername
of the user. - Line 209: we attach the
changeUsername()
function to theonClick
event listener of a button. This will change the username of the user based on the input provided inLine 208
. - Line 216: we attach the
toastRef
which we created earlier on to the paragraph withid
oftoast
. And attach thenotification
state to it. Similar to our previous explanation. This will display the toast for any notification message. - Lines 221–227: Here, we map the
allChats
state array. It will map all the chats by displaying theid
,chat
, andtime
of the chat. - Lines 233–239: we check if there is someone typing by checking if the
someoneTyping
state has a value. If true, we use the logicalAND
operator to display thesomeoneTyping
state value. - Line 240: on the form that gets to submit a chat, we attach the
submitChat()
function we created earlier on to itsonSubmit
event handler. - Line 241: we set the value of our input field to
chat
. TheonkeyUp
andonKeyDown
event handlers were attached to the input field used to type our chat. The former is when a user stops pressing any key on the keyboard, thereby setting thesomeoneTyping
state value tonull
. While the latter is responsible for when a press is done on any key on the keyboard. Also attached is theonChange()
event handler that will invoke thehandleChatInput()
function. And lastly, we have theonKeyPress
event handler which will check if a user hits or presses the key'Enter'
on their keyboard.
Previewing Our Project
You can see the live application on https://oya-chat.onrender.com/.
Here is the link to the full code on Github. Remember to leave a star ⭐️ if you love the project 😊.
Conclusion
In this tutorial, we have learned how socket.io and real time communication work. We have also learned how to use CORS, useEffect and cleanup, useRef, and Tailwind CSS. With this, we can build any real-time chat application.