Click here to share this article on LinkedIn »

WebSockets can be very beneficial for building realtime communication or data streaming applications on the web, such as chat apps and applications that stream images or other types of media. Furthermore, you can easily set up a connection to any socket endpoint utilizing the JavaScript WebSocket API of your browser.

In this tutorial we are going to build a simple chat app based on WebSockets with socket.io and React. As always, the full source code is available here.

Why WebSockets instead of HTTP(S)?

With WebSockets you can establish a persistent connection, which allows for bidirectional communication between client and server. Nowadays, you will mostly find REST based APIs on the web, which are built upon HTTP. These APIs are consumed as follows: The client requests a page or resource and the server responds (request-response). Thus, using WebSockets over HTTP can have the following advantages:

1. Realtime Communication

In a request-response scenario, there is no way for a server to send data to the client without having the client to request something first. A client would have to continously ask for changes in regular intervals (polling), which is not what we consider realtime. Imagine a chatting app, where you would only see new messages every 30 seconds. Pretty annoying…

There are ways for a server to notify a client upon new events via HTTP messages. You could push messages to a service worker running in the client’s browser or have the client subscribe to server-sent events (SSE). With WebSockets however, we get this feature for free and can easily broadcast new messages received to every other client connected.

2. Less overhead

HTTP is a stateless protocol, thus the overhead of a HTTP header is added to every single message, which can get quite large in size. This will especially make an impact for frequent messages with a relatively small payload (for example in a chatting app).

Moreover, a HTTP connection will usually only keep alive for a certain amount of requests and will be closed after some time of being idle. Thus connections will quite frequently have to be reestablished, which introduces an initial setup time due to the TCP 3-Way Handshake (and exchanging certificate and key pairs in case of HTTPS).

3. Stream processing

With WebSockets one can essentially stream binary data of arbitrary size between client and server. Thus it’s well suited for stream processing tasks such as image processing apps, which might stream images or video data back and forth.

I think you get, why WebSockets might be useful …

Let’s finally code the app!

For this, we will use the socket.io npm package, which provides us with a node.js WebSocket API for our chat server and a JavaScript client for the browser side. This way we do not have to mess around with sending actual binary data, as this package will kindly serialize our data to JSON.

In the following I will discuss the entire source code in detail. I will focus on the interesting bits of the application in order to give you an introduction to socket.io.

So, what are we going to build?

Nothing too crazy. Once a client connects we want the user to be able to select one of the characters. Afterwards he or she is able to join and leave chatrooms and send messages to other users in the same chatroom. Here is a quick demo to visualize what I am talking about:

Let’s get started!

Listening for incoming socket connections

In this example we will attach socket.io to a simple HTTP server. If you want to, you can also easily use socket.io in conjunction with express.

With io.on(‘connection’, cb) we are listening for incoming socket connections. Once a new client connects, we can attach multiple event listeners to the stream. The error and disconnect events are predefined. The other events are custom events I introduced to implement the chat API.

The server is listening for socket clients to connect on port 3000, which we will do in the following.

Connecting to the socket server with a client

Note, on the client side we want to require socket.io-client. In case you are not using a bundler (I am using webpack here) you will have to include the io client script into your document.

In our client we can listen to events with socket.on and remove the event listener with socket.off respectively. We will use registerHandler later to register a onMessageReceived callback in our Chatroom component, in order to update the components state and display new messages once received.

With socket.emit we can emit the custom events, which our chat server listens to. As the second argument we can pass the actual data. Furthermore, we can even implement a request-response type communication by passing a callback as the third argument, which we can use to receive a response from the chat server after emitting an event on the client or the other way around. This is pretty neat and we will use this quite a lot later on in this example.

Implementing the API

On our chat server we will keep track of all connected clients and which character they selected (ClientManager) as well as the state of each chatroom, e.g. the list of users that joined a chatroom and a history of all messages sent to that chatroom (ChatroomManager).

First thing we want to add, is an endpoint for the client, to select one of the characters:

We will stick to the callback(error, result) pattern and invoke the callback with an error message as the first argument in case of an error. Essentially what we are doing here, is checking if the selected character is available and assign the client to that character, otherwise notify the client with an error message.

More interesting stuff happens, when a user joins, leaves a chatroom or sends a message. The corresponding handlers for these events use the following helper function:

It ensures that the chatroom passed is valid and that a character has been selected upfront and “user left/joined chatroom” or the message sent by the user is added to the chat history of the chatroom. Furthermore we broadcast the event to all users in the chatroom.

The implementation of Chatroom is rather simple:

For simplicity the chatHistory is just an array and we hold a mapping of all users in that chatroom, so that we can emit a “message” event to all clients in broadcastMessage.

As the actual handlers resemble each others functionality, I will only show the join handler here:

After handling the event, we add the user to the chatroom and respond with the current chat history.

Finally, once a client disconnects, we also make sure to remove the user from all chatrooms and remove the client information, which will free the selection of the character for other users:

Displaying messages in realtime on the client

To our chatroom component we add the following:

Once we enter a new chatroom we initialize the chat history returned by the chat server after receiving a “join” event. As promised earlier, when the component mounts, we register the onMessageReceived callback in the handler from our client API. Once registered, the client will receive the message events broadcasted by the chat server. This way we can update the components state with the new message and rerender the chat activity immediately.

Final Words

That’s already about all of the interesting stuff that we are doing with WebSockets here. The rest is just about building a UI with React and material-ui. So if you are interested in that, you might want to check out the source code. Other then that, I hope you feel confident with WebSockets and socket.io after reading this tutorial. Happy coding!

If you liked this article you are invited to leave some claps and follow me on medium and/or twitter :). Also feel free to leave a star on the github repository. Stay tuned for more tutorials!