Create an Open Group Chat Using ExpressJS (NodeJs), Socket.io, ReactJs, and Tailwind CSS

Theodore Kelechukwu Onyejiaku
Geek Culture
Published in
10 min readAug 24, 2022
A Demo of our Application

What you will learn

  1. Socket.io
  2. CORS
  3. React useEffect, dependencies, and Cleanup.
  4. React useRef
  5. Tailwind CSS
  6. 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

  1. We make connections between clients and a server.
  2. 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 the on method that will take the name of the event we emitted, for example socket.on(‘eventName'). There is also the broadcastmethod 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:
Communication Between Server and Clients as a Client Types on the Keyboard

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:

Source code for the app.js of our server
  • 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 asid 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 the id and chatof the user that sent the chat, it then emits the sendChat 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 the resetChat event.
  • Line 34: the userTyping event is created which gets fired when a user is typing. And in turn, it emits the event someoneTyping to other clients.
  • Line 39: we fire the activeUsers event which gets the number of connected users. It uses the socket.io engine.clientsCount method to achieve this. And then we emit the result to the clients using the countUsers 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 the disconnectNotification 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 contentfield 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:

Source Code for App.js
  • Lines 1–3: we import React , useState useRef and useEffect .
  • Line 4: we import io as our client engine. Here is the guide to importing socket.io on a client.
  • Line 5: we import customized CSS.
  • Line 9: we create the toastRef using the useRefReact hook which will handle the element on the DOM that will be responsible for toasts.
  • Lines 12–20: with the useState hook, we create variables such as activeUsers which represents the number of connected devices. socket which represents an instance of a client or socket that is connected. id which represents the id 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 a setTimeOut() 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 the usernameChange event.
  • Line 49: we create the handleChatInput() method which will take what the user has typed in and set the value to chat .
  • Line 54: with the submitChat() function, we first check if the client is disconnected using the socket.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 event chat is emitted which takes the id of the user, their chat, and their socket id as socketid . And then finally, when the chat is submitted, the scrollToBottom() 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 a timeout function that runs after every second. It emits the userTyping event and is been cleared using the clearTimeout() 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 of socket.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 state id.
  • Line 129: we create the event handler for countUsers which will get the number of clients connected and set the value to the state activeUsers by using the function setActiveUsers().
  • Line 135: the handler for the event sendChat is created and it gets the allChats state array and adds the new chat to it. And finally, it calls the scrolltoBottom() function.
  • Line 143: here we create the handler for the resetChat event. It modifies the allChats state by changing the username of a user.
  • Line 150: now we create the handler for the message event. It triggers when the message event is emitted and notifies every socket with the message using the setNotification() function and the showToast() function.
  • Line 156: we add a clean-up function to our firstuseEffect 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 state allChats 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 the socket 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 the searchUser() 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 the username of the user.
  • Line 209: we attach the changeUsername() function to the onClickevent listener of a button. This will change the username of the user based on the input provided in Line 208 .
  • Line 216: we attach the toastRef which we created earlier on to the paragraph with id of toast . And attach the notification 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 the id , chat , and time 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 logical AND operator to display the someoneTyping 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 . The onkeyUp and onKeyDownevent 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 the someoneTypingstate value to null. While the latter is responsible for when a press is done on any key on the keyboard. Also attached is the onChange() event handler that will invoke the handleChatInput() function. And lastly, we have the onKeyPress 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.

--

--

Theodore Kelechukwu Onyejiaku
Geek Culture

Software Developer(JavaScript). And I like writing poems too.