WebSocket is a popular communication protocol (TCP) that enables seamless full-duplex communication (two-way communication) between client and server. In other words, it allows websites send and receive data without delay. WebSockets do not use the http:// or https:// scheme (because they do not follow the HTTP protocol). Instead, WebSocket URIs use a new scheme ws: or wss: (recommended) for a secure WebSocket. Web developers use WebSocket to build chat applications, multiplayer games, SDKs, or user interfaces exposing server-side services in real-time and so much more.
In this tutorial, we'll be building a simple chat app that allows users to talk to each other.
Before following this tutorial, you need the following:
Express: lightweight and flexible Node.js web framework that provides robust set of features for building applications.
Socket.IO: enables real-time bidirectional event-based communication. Socket abstract WebSocket connections. It uses WebSocket for transportation and then fallback to long polling when Websockets fail to establish a connection.
Nodemon: a tool that helps develop Node.js applications by automatically restarting the node application when file changes in the directory are detected.
Check out the demo. If you want to dive straight into the code, it's on GitHub.
Setting up Express Server
Let's create the project directory and initialize the node project. Run this command on your terminal:
mkdir webcage && cd webcage && npm init --y
You should have something like this:
Open your code editor, and you should have a package.json file scaffolded for you. Run this command to install the dependencies:
npm install express moment socket.io
Also, we will install Nodemon as a dev dependency. Run this command:
npm install -D nodemon
Let’s add some scripts to the package.json file. Replace the properties of the script JSON object with this code snippet:
Let's create an index.js file in the root directory. This will be the entry point of our app.
Add these lines of code:
Then start your dev server with this command:
npm run dev
You should get something like this:
Perfect!! We have successfully set up the express server.
Serving static assets
Here, we are specify that we want to serve static assets from the public directory. Next, let’s create the public directory directly within the project root.
Inside public directory, create an index.html file will the following content:
This will serve as the homepage/login screen to our chat application. To be begin chatting, users will have to provide an email address, a username, and a room/channel they want to join.
We can get the content of the referenced CSS file from the GitHub repo.
Now, navigating to http://localhost:3000, we should see something as below:
Handling Socket.IO on the Server
Replace existing code in index.js with this:
Let's break down the moving parts of the codebase.
Firstly, we create an Express server then use it to initialize a socket.io server.
With the socket server instance initialized, we can now emit and listen to events between the server and client. In our code, we started by listening to a joinRoom event from the client. This event handles the following:
A new client joining a room
The general message broadcasted when new users connect
Current users in a room
Next, the server listens for the client messages. Here, the server emits the client message to the current room.
Note: Rooms are server-only.
Finally, we run this when a client exit a room. It will remove the current user from the room and update the list of active users.
You will notice we are using some helper functions in our server code. Let's go ahead and add them now. In the root of your project, create a helper directory, and create these two files formatDate.js and userHelper.js.
This function takes in the username and message as arguments, and returns an object containing a formatted time, username, and message. Just like this:
This helper function handles all the user behaviour.
At this point, we have successfully handled socket.IO on the server-side.
Handling Socket.IO on the Client
Now, let’s move to the frontend. Within the public directory, create a chat.html file with the following content:
In this snippet, we made the following changes:
Added an id of leave-btn for the Leave Room button. We will use this in our frontend code to prompt the user to confirm they are leaving a room
Included client-side Socket.IO module.
Included QS library via CDN. We will be using it to parse URL string.
main.js is where all our main frontend application.
Inside the public directory, create a js directory and inside it a main.js file with the following code:
Firstly, we use the DOM to target HTML elements that will be updated. Then, we use the QS library to parse username and room from the URL. Setting ignoreQueryPrefix to true will omit the characters in the URL string.
For example: http://localhost:3000/chat.html?email=samueldoe%40example.com&username=Samuel&room=Business will result in:
Next, we initialised client-side socket.io. Then, we emit the joinRoom event to the server. Earlier, we saw how the server listened and handled this event.
Let's go ahead to receiving events the server sent. Add these lines of code:
Remember, the server emits the roomUsers event, which sends the current users in a room and the room name. In our frontend code, we listen to this event and use the DOM to update the room name and active users list. We will handle the outputRoomName(name) and outputUsers(users) functions much later.
Add these line of code:
The server emits a message event and sends "Messages are limited to this room!" as payload. On the frontend, we listen to the event and update the UI with the payload. We'll handle the outputMessage(message) soon. The chatMessages.scrollTop = chatMessages.scrollHeight enables the page to always scroll to the latest message.
Let's now handle the message box. Add these lines of code:
Here, we begin by adding a submit event listener and preventing the form default behaviour with e.preventDefault(). Next, we target the text value in the message box. In chat.html, the form input has an id of msg, and so we target the element by using e.target.elements.msg.value. The msg.trim() removes whitespace from both sides of the message string. If there is no message, end the function call. On the other hand, if there's a message, emit the chatMessage event and the message as payload to the server. Finally, clear the message box and add keep focus on it.
Now, let's write the functions handling UI state. Add these lines of code:
This is just DOM manipulation. The message argument is an object that has text, time, and username keys. Remember, that the time is formatted with Moment in our formatDate.js helper function. The function will return a div with the message a user types in the message box.
Next, we update the name of the room the user is currently in. Add the lines of code:
The room argument has the room as payload.
Next, we update the current users in a room. Add these lines of code:
The users argument is an array object containing the id, room, and username of a user. When you log it to the console, you get this:
Finally, we confirm the user wants to leave a room. Add these lines of code:
Now let’s test the app. Restart your dev server and navigate to http://localhost:3000 in two different browser windows. You should have something like this:
In this tutorial, we briefly discussed the WebSocket protocol, and we hinted that chat apps are one of the applications of the technology. We went ahead to build a simple chat app with Node.js and Socket.IO. Though Socket.IO is basically a wrapper of the Websockets API, and hence it uses the WebSocket protocol for connection.