Learn WebSockets with NodeJS and ReactJS

When to use it, When not to use it, and some Gotchas.’

WebSocket is a technology that enables us to exchange data between the web server and a client in real time.

WebSockets is a fascinating technology; every web developer should have at least a basic understanding of it.

Today we will learn about WebSockets. We will also learn how to implement a simple WebSocket server using Socket.io in a NodeJS application.

Let’s get started!

What is WebSockets?

In simple terms, this is a protocol. HTTP is also a protocol. But the difference is HTTP requests are unidirectional, but Websockets are bi-directional.

Some other characteristics are

  • Websockets use a single TCP connection.

  • WebSocket connections are persistent.

  • It’s usually more efficient than HTTP connections.

How does WebSockets work?

Websocket connections work in two steps

1. Handshake

The client sends an HTTP request to the server, and if the server agrees to establish a WebSocket connection, the handshake is complete.

2. Exchange

Once the connection is established, both parties can send data back and forth.

So when to use Websockets?

Glad you asked; WebSocket technology provides several benefits over traditional HTTP requests, including:

You need Real-time data exchange

If you are building a group chat or a stock market application, Websockets are your best bet.

You care about latency:

If latency is your biggest concern (Maybe you are building a multiplayer game), then WebSockets are the way to go.

Improved efficiency:

WebSocket connections are more efficient than traditional HTTP requests, using a single TCP connection.

You should have a good idea of when to use WebSockets. Let’s implement it in a demo application.

How to use WebSockets in NodeJS

In NodeJS, we use a powerful library called Socket.io to create WebSocket servers.

Today we will build a simple Websocket server in NodeJS and communicate with it from a ReactJS application.

Let’s get started.

How Socket.io works?

Socket.io is a library that enables real-time, bidirectional communication between a server and a client over the web. Here is a brief overview of how the Socket.io server and client communicate:

  1. The Socket.io server starts listening for incoming client connections on a specific port.

  2. When a client wants to connect to the server, it sends a request to the server with the necessary connection information.

  3. Once the server receives the connection request, it establishes a WebSocket connection with the client. If WebSocket is unavailable, the server will use other fallback methods, such as long polling.

  4. Once the WebSocket connection is established, the server and client can send data to each other in real time. The data is sent as events, which can be custom messages that carry any data.

  5. Both the server and client can emit events to send data to the other party and listen for events to receive data.

  6. The Socket.io library handles the underlying communication protocol details, such as message encoding/decoding, connection management, and fallback mechanisms.

Socket.io enables real-time, bidirectional communication between a server and client by establishing a WebSocket connection, sending events with data between the two parties, and managing the connection details using the Socket.io library.

Setting up the server

Let’s start with a basic express application. Hopefully, you have one already; else, you can use the following boilerplate.

    git clone https://github.com/Mohammad-Faisal/express-typescript-skeleton-boilerplate

Now, first, install the socket.io package inside our express project.

    yarn add socket.io

Then open up the index.ts file, our server root, and add the following lines of code.

import express, { Application, Request, Response } from "express";
import bodyParser from "body-parser";
import http from "http";
import { Server } from "socket.io";

const PORT = 4000;

// Create an express applicaiton
const app: Application = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// Create a Socket.io server with the express application
const server = http.createServer(app);
const io = new Server(server);

// wait for new connection
io.on("connection", (socket) => {
  console.log("New client is connected");

  socket.on("disconnect", () => {
    console.log("Client disconnected");
  });
});

// Run the server
try {
  server.listen(PORT, (): void => {
    console.log(`Connected successfully on port ${PORT}`);
  });
} catch (error: any) {
  console.error(`Error occurred: ${error.message}`);
}

So with this code,

  • We are creating a server with the express app.

  • We are creating a new socket.io server instance.

  • We are waiting on any client that wants to connect with the application and display the message.

  • And start listening on port 4000

We are done with the server. Let’s head over to the client application.

Setting up the client:

Let’s create a boilerplate React application,

    yarn create react-app --template typescript

Now install the following dependency on your react application. This is the socket.io client required to communicate with a socket.io server.

    yarn add socket.io-client

Then initialize the client at the top of your component.

import io from "socket.io-client";

const socket = io("http://localhost:4000");

The above code will try to establish a WebSocket connection with the passed URL.

Unfortunately, you will see that you are getting a cors error on the console. That’s because we are not allowing our javascript(React’s) origin to communicate with our server.

So let’s go back to the server code and update the socket io configuration to avoid the CORS issue.

const io = new Server(server, {
  cors: {
    origin: "*",
  },
});

Here we are allowing all javascript origins using “*”.

which is obviously bad for security; only enable your expected URLs in production.

Your cors errors will be gone once you restart the server.

Now if you refresh your react application on the server side, you will see the sweet message,

New client is connected

Let’s have some communication between the client and the server.

Publish an event

As discussed earlier, we can emit events from socket.io server, and all the clients that are subscribed to that event will be notified.

Let’s create a fake endpoint that will emit some message once hit. Add the following code to your server.

app.get(
  "/test-message",
  async (req: Request, res: Response): Promise<Response> => {
    // notice here
    io.emit("message", "Hello World!");

    return res.status(200).send({
      message: "message emitted",
    });
  }
);

This is a simple GET endpoint. If someone hit it an event will be emitted.

If you look closely, you will see that we are calling io.emit with two parameters.

message -> it is the event name.

Hello World -> this is the event payload.

So now, all of the clients who are watching this event will receive this update.

To test it, let’s make our React application subscribe to the message event.

Subscribe to the event

Now inside the component, add the following code.

useEffect(() => {
  socket.on("message", (message) => {
    console.log("received a new message from the server", message);
  });
}, []);

Here we are subscribing to the message event via socket.on() method. So whenever our server emits an message event, our client will be notified.

Test it

Now go ahead and hit the localhost:4000/test-message and see the client console.

client receives a message

So now we can understand the power of WebSockets. It can notify as many clients as you want.

Client-to-server communication

Now we can also send messages from the client to the server. Let’s say on the click of a button, we want to send a message to the server and view it.

Add a button and an onClick handler to our React component.

const sendMessage = (event: any) => {
  event.preventDefault();

  socket.emit("client-message", "This is from client");
};

return (
  <div>
    <button onClick={sendMessage}>Send message to server</button>
  </div>
);

As you can see, the above function can emit an event. Just like our server previously, we can listen to this event on our server side.

io.on("connection", (socket) => {
  socket.on("client-message", (msg) => {
    console.log("message: " + msg);
  });
});

We will see the message reach the server when we click the button.

Server receives a message

Now we have a two-way communication system that we can use in many ways.

Some things, Good to know

You need to have some important considerations when designing large systems using WebSockets.

Delivery Guarantee

When you send a message from the server to the client, it’s guaranteed to be delivered.

At most once

But if you are sending messages from the client to the server, it’s a bit different. Your message is guaranteed to be delivered.

At least once

So design accordingly and plan for failure.

Ordering of the messages.

The messages will be received in order, No matter what.

For example, the following events,

socket.emit("event1");
socket.emit("event2");
socket.emit("event3");

In the example above, the other side will always receive the events in the same order.

When not to use WebSockets?

There are no silver bullets in the technology world. Everything is a trade-off.

And you should be aware of WebSockets' limitations before introducing it into your architecture.

Here are some of the most significant limitations of WebSockets:

Browser support:

While all modern browsers support WebSockets, older browsers may not. This can be a problem if you must support a wide range of browsers.

Firewall issues:

WebSockets use a different protocol than HTTP; some firewalls may block WebSockets traffic. This can be a problem for users behind firewalls that block WebSockets.

Connection overhead:

WebSockets require a dedicated connection between the client and server, which can add overhead and increase the load on the server.

Scalability:

WebSockets can be resource-intensive and may need to scale better for applications with many clients.

Security concerns:

WebSockets can be vulnerable to attacks like cross-site scripting (XSS) and cross-site request forgery (CSRF).

Compatibility with existing infrastructure:

WebSockets may need to work better with existing infrastructure, such as load balancers, proxy servers, and content delivery networks (CDNs).

Conclusion

Today I tried my best to introduce WebSockets and Socket.io as best as possible.

I hope you learned something new today. Have a wonderful day!

Github Repo: