Chat Rooms With Socket.io

Akilesh Rao
The Startup
Published in
9 min readJan 29, 2021

Alright, so you’re sitting on your couch reading tweets about political instability in the US and how it’ll affect you even though you don’t live there. The very next minute you’re scrolling through cat asmr posts on instagram because why not. After about 20 minutes of feline purring therapy, you start to have an elaborate discussion with your friends about how you can run your country better than the government. The above activities have one thing in common. You need to get a job. Other than that, if you noticed, everything’s happening in real time. The endless stream of tweets and instagram posts, the asynchronous nature of a group of friends chatting, all happen instantaneously, in real time. Ever wondered how this happens? You guessed it, sockets.

Sockets will essentially establish a bi-directional communication channel between the client and server which means that the server need not wait for the client to send a request before sending a response. It can send data back to the client independently without any requests. It’s like a phone call. You don’t have to wait for anyone to speak first and only then, reply back. Anyone can speak in any order without waiting for each other.

Web sockets is the web-based implementation for sockets. It’s a lot more efficient than your traditional REST based request-response cycle. There’s a lot of use cases for web sockets, spread across multiple industries. You can read this article to check out a few common applications.

Getting started

This is what we’ll be building today. You can find the demo here.

Final app

Back end

1. Initial setup

The back-end is built with node.js. Make sure node.js is installed on your machine before moving forward. Inside an empty folder type the following command to setup a node project.

npm init -y

After that, install the required dependencies

npm i cors express socket.io

The next step would be to create a file in the root folder called index.js. This will be your server. Copy the boilerplate code

const app = require('express')()
const http = require('http').createServer(app)
const cors = require('cors')
const PORT = process.env.PORT || 5000
app.use(cors())app.get('/', (req, res) => {
req.send('Server is up and running')
})
http.listen(PORT, () => {
console.log(`Listening to ${PORT}`);
})

What we’ve essentially done is

  • Initialized an express app as a function handler and passed it to http-server
  • Defined a root route handler
  • Made the http-server listen to port 5000

You’ve got yourself a basic express app running on localhost:5000. Before adding in the socket.io code, let’s quickly understand a few key concepts.

Connection : A bi-directional link between the client and the server.

Emitter events : Events that are emitted along with some data

socket.emit('event-name', data) 

Listener events : Events that listen for data from emitter events

socket.on('event-name', callback(data)) 

Rooms : A server-side-only concept that lets you create a room inside which you can listen to or emit events from multiple sources

socket.to('room-name').emit('event-name', data)

That’s all the information you need for our implementation. Let’s move on to setting up the socket connection.

2. Setting up a socket connection

A socket connection is a bi-directional link between the client and the server. The code for client-side connection will be covered later, in the client section. The server-side connection code will look something like this

const app = require('express')()
const http = require('http').createServer(app)
const cors = require('cors')
const PORT = process.env.PORT || 5000
//Initialize new socket.io instance and pass the http server to it
const io = require('socket.io')(http)
app.use(cors())io.on('connection', (socket) => {
socket.on("login", ({name, room}, callback) => {

})
socket.on("sendMessage", message => {

})
socket.on("disconnect", () => {

})
})
app.get('/', (req, res) => {
req.send('Server is up and running')
})
http.listen(PORT, () => {
console.log(`Listening to ${PORT}`);
})

Basically,

  • Initialize a socket.io instance and pass http-server as a parameter.
  • Invoke it’s “on” method with “connection” as the event name and a callback function. This callback function holds your socket instance which can then be used for emitting or listening to events.

In our case, we have 3 events to listen to

  • A login event
  • A send-message event
  • A disconnect event

Since these are listener events, they will be triggered from the client-side and we’ll simply handle what to do inside these events once they’re triggered.

Before moving on to business logic for these events, we’ll be creating a new file called users.js. Since socket.io rooms is a server-side-only feature, we need to manage the user list on the server. This file acts as a user database for our room.

  • The users are maintained inside an array.
  • addUser method takes in the user ID (in our case the socket id), the user name and the room in which user has entered. We check to see if there’s already an existing user or if the form’s not filled properly. If yes we return an error else we return the added user after pushing it to the array.
  • The getUser method will fetch the user based on it’s id.
  • The deleteUser method will delete the user based on it’s id.
  • GetUsers will return all the users present in the array.

This can now be used in our index.js file as a database. Inside index.js add the following import statement

...
const PORT = process.env.PORT || 5000
const {addUser, getUser, deleteUser, getUsers} = require('./users')
...

Now, coming back to the events inside our socket connection, they’ll look something like this

  • The login event listener will take the name and the room, passed in from the client side and pass it to the addUser method along with the socket id(Socket ID is unique for every user who logs in so it indirectly acts as the user id).
  • If there’s an error(from our addUser function) we return it in our callback.
    If the user is added successfully, we use the socket.join(user.room) to add the user to the room.
  • We then use socket.in(‘room-name’).emit(…) which basically means that the event will be emitted only to users inside that particular room. The event that we’re emitting is a notification event. This event will simply emit a message to the client side that can be displayed as a notification.
  • Finally, a users event is triggered using io.in(‘room-name’).emit(…) which emits the updated user list to the client.

socket.in and io.in are essentially the same functions. The only difference is socket.in will not include the sender, so in our case the notification event will be triggered for every other user in the room except ourselves.
io.in on the other hand includes the sender as well when emitting an event, so the users list after a user login is updated for everyone inside the room.

  • The sendMessage event listener will simply take the message sent from the client’s end and emit a ‘message’ event in the room(using io.in) with the username and the room name.
  • The disconnect event listener will delete the user using our deleteUser method by passing in the socket ID/user ID and then emit a notification event, to send a notification message to the users in the room, and a users event to update the list of users in the room just like in the login event listener.

With that, your back end is ready. Moving on.

Front end

1.Initial setup

I assume you are somewhat familiar with React and it’s common practices.
Inside an empty folder create a react project.

npx create-react-app 'your-app-name'

After that, inside your src folder, create a components folder which will have 2 sub folders for Login and Chat. Both these folders will have a JS file and a styles file. I also have 3 separate files for managing state on the front-end, using React’s Context API.

Once you have your folder structure setup, you need to install the dependencies. Type the following command

npm i node-sass@4 react-icons react-router-dom socket.io-client react-scroll-to-bottom @chakra-ui/react @emotion/react @emotion/styled framer-motion

As you can see, I’ve added a UI library called Chakra UI. It’s an elegant looking design component library with a very developer friendly documentation. Makes it a lot easier for developers to add basic styling quickly. Definitely, one of my favorites. You can read more about Chakra UI, here.

2. State Management

Once all the dependencies are installed, open your App component and replace it with the following code.

This might look a bit confusing but what’s happening here is that the entire App is being exposed to different context providers. In simpler words, data inside ChakraProvider, MainProvider, UsersProvider & SocketProvider can be accessed anywhere throughout the application.

  • Chakra Provider is a wrapper from Chakra UI.
  • Main Provider holds the user name and room name. You can access it or change it throughout your application.
  • Users Provider holds the users present inside a room. Again, you can access the list or update it anywhere in your application.
  • Socket Provider holds the socket instance which we’ll be creating right after this section.

I won’t go deep into state management in this tutorial. You can read a small article on how I normally handle state with context API, here.

Remember how we established a socket connection on the back end using io.on(‘connection’), it’s time we connect it to the front end. Open your socketContext.js and copy the following code.

  • We initialize an instance of socket from socket.io-client.
  • Inside our provider we create a constant called ENDPOINT and store our back end URL inside this constant.
  • Then we call io with the ENDPOINT and a few other configurations to avoid CORS issues. This io instance is than exposed to the entire application so that it can be accessed from anywhere.

And that’s pretty much it. You can now use this socket instance anywhere in your application to emit or listen to events from your back end.

3. Login

  • The login file mainly does 2 things. It opens up a socket event listener inside a useEffect for users which means that it’ll continuously listen to the users event being emitted from the back end whenever a new user is added.
  • The second part is a login function which is a validation method to check if the form is filled correctly or if the username already exists.
    (Click here to see the complete file)

Once you fill the form correctly, you’re redirected to the Chat Page.

4.Chat

The Chat component consists of 3 main components.

  • A header, which shows the user name and the current room along with a button to see the current set of users in the room and a log out button.
  • The main body which will have all the messages.
  • A footer which consists of the input in which the user types his/her message and a submit button.
  • First useEffect will check if the name state from mainContext.js is empty or not, if empty the user will be redirected to login page
  • Second useEffect will have 2 socket event listeners for message and notification. Whenever a message event is emitted from the back end, this listener will store the message in a messages array locally.
    In case of a notification event, it simply shows the notification message from the back end in a toast.
  • The handleSendMessage is an event emitter which gets triggered when the user sends a message. This is picked up by the back end’s event listener which basically emits it again to the entire room.
  • The logout method simply clears the username and room name and then redirects the user to login page.
    (Click here to see the complete file along with styles.)

With that you’ve now created a fully functional chat application with Socket.io. Serve your front end and back end on their respective ports and if everything works, you’ll find an application similar to this

If you don’t want to do any of this, clone both the following repositories and follow their respective readme files for installation.

Also, some additional links that might be helpful for you

I also post programming content on youtube regularly, so do check it out, here. If there’s anything else that you’re stuck with, feel free to put it down in the comments. Cheers!

--

--