Setting up a Node.js WebSocket server

Leslie Gyamfi - Sep 14 '23 - - Dev Community

Real-time communication has been of increasing significance in our digital age. Despite being appropriate in some circumstances, conventional HTTP-based communication is not intended for long-lasting, low-latency communications. WebSockets can be used in this instance.

Using a single TCP connection, WebSockets enable real-time data transfer by establishing a bi-directional communication channel between a client and a server. The setup of a WebSocket server using Node.js will be covered in detail in this post. We can create reliable WebSocket servers that can meet the needs of real-time web applications by utilizing JavaScript's capability on the server side.

Why should we employ WebSockets in our development processes? There are several reasons, let us look at some of these reasons in the following chapter.

Usefulness of WebSockets

  • Scalability: Because WebSocket connections are built to support several concurrent clients, they are appropriate for applications that need great scalability. WebSocket servers are capable of handling hundreds or even millions of connections at once by effectively managing connections.

  • Relatively Lower Latency: By removing the need for repeated HTTP queries, WebSockets lower network latency and overhead. Instant data transfer is made possible by the permanent connection that WebSockets provide, resulting in a more responsive and engaging user experience.

  • Cross Domain Support: WebSockets make it simple to communicate between many domains, allowing programmers to create applications that integrate a number of servers or services. This adaptability is particularly helpful in situations when data must be transferred across several origins.

Now that we have looked at some of the usefulness or advantages of WebSockets, let us look at how to initialize a WebSocket server in Nodejs.

Installing dependencies

Before we can set up a WebSocket, we'll first have to install some required dependencies. We'll employ the ws library for the purpose of this article. This library provides efficient WebSocket establishment for Nodejs.

To install the ws library,

Open your terminal, navigate to your project directory, and run the command below:

npm install ws
Enter fullscreen mode Exit fullscreen mode

The next chain of command is to initialize the WebSocket and we're looking at that in the chapter below:

Initializing a WebSocket server in Node.js

Once the ws library is installed, let us proceed to initialize the WebSocket server in Node.js. The server will listen for incoming WebSocket connections, handle communication with clients, and facilitate the exchange of real-time data.

To create a basic WebSocket server, let us create a new JavaScript file, say, server.js, in the project directory.

Next, let us require the ws module at the beginning of the file with the code below:

const WebSocket = require('ws');
Enter fullscreen mode Exit fullscreen mode

Let us now define a variable to hold the WebSocket server instance:

const wss = new WebSocket.Server({ port: 3000 });
Enter fullscreen mode Exit fullscreen mode

Here, we are creating a WebSocket server that will listen on port 3000. Let's add event listeners to handle WebSocket connections and messages:

wss.on('connection', (ws) => {
  // Handling new WebSocket connections
});

wss.on('message', (message) => {
  // Handling incoming messages
});
Enter fullscreen mode Exit fullscreen mode

In the first event listener, connection, we can define the logic to handle new WebSocket connections. This code block will be executed each time a client establishes a WebSocket connection with the server.

Similarly, in the second event listener, message, we can define the logic to handle incoming messages from clients. This code block will be executed whenever the server receives a WebSocket message from a connected client.

Let us start the WebSocket server by adding the following line of code at the end of the server.js file:

console.log('WebSocket server is running...');
Enter fullscreen mode Exit fullscreen mode

This will print a message in the console indicating that the WebSocket server is up and running.

Handling WebSocket connections

Once a client establishes a WebSocket connection with the server, it is crucial to handle and manage these connections effectively.

Inside the connection event listener, we can access the WebSocket connection object (ws) representing the newly connected client. We can perform various operations, such as sending initial data, tracking connected clients, or performing authentication.

wss.on('connection', (ws) => {
  // Send initial data to the client
  ws.send('Welcome to the WebSocket server!');

  // Track connected clients
  // ...
});
To handle disconnections, you can listen for the WebSocket close event:

ws.on('close', () => {
  // Code to handle client disconnection
});
Enter fullscreen mode Exit fullscreen mode

This event will be triggered when a WebSocket connection is closed by the client or due to some error. You can use this event to clean up resources associated with the client or update the list of connected clients.

Handling WebSocket messages

WebSocket communication primarily revolves around exchanging messages between the client and server. Let's explore how to handle incoming messages from clients in Node.js.

Inside the message event listener, we can access the received message and take appropriate actions based on its contents.

wss.on('message', (message) => {
  // Handle incoming messages
  console.log('Received message:', message);

  // Send a response to the client
  ws.send('Message received successfully!');
});
Enter fullscreen mode Exit fullscreen mode

We can perform custom logic based on the received message, such as updating the application state, broadcasting the message to other connected clients, or triggering specific actions.

To send a message to a specific client or all connected clients, you can use the send method of the WebSocket connection object (ws).

// Send a message to the client that triggered the event
ws.send('Hello client!');

// Broadcast a message to all connected clients
wss.clients.forEach((client) => {
  client.send('Broadcast message!');
});

Enter fullscreen mode Exit fullscreen mode

By utilizing the send method, you can establish seamless bidirectional communication with clients, enabling real-time updates and interactive features in your application.

Data serialization and deserialization in WebSocket communication

WebSocket communication involves the exchange of data between the client and server in a structured format. To ensure compatibility and seamless communication, it is essential to serialize and deserialize data appropriately.

Data Serialization

Serialization is the process of converting data objects into a string representation that can be transmitted over the WebSocket connection. JavaScript Object Notation (JSON) is a widely used data interchange format that provides a simple and human-readable way to represent structured data.

To serialize data into JSON format, we can use the JSON.stringify() method provided by JavaScript. Here's an example:

const data = { name: 'John', age: 25 };
const serializedData = JSON.stringify(data);
Enter fullscreen mode Exit fullscreen mode

The JSON.stringify() method converts the data object into a JSON string, which can be sent as a message over the WebSocket connection.

Data Deserialization

Deserialization is the reverse process of serialization, where the received string representation of data is converted back into its original object form. In WebSocket communication, you need to deserialize incoming JSON strings to access and process the data. Let's use the JSON.parse() method to deserialize a JSON string into an object. Here's an example:

const receivedMessage = '{"name":"Jane","age":30}';
const deserializedData = JSON.parse(receivedMessage);
Enter fullscreen mode Exit fullscreen mode

The JSON.parse() method converts the receivedMessage string into a JavaScript object, allowing you to access its properties and perform further operations.

Sending Serialized Data

To send serialized data over a WebSocket connection, we can use the send() method provided by the WebSocket connection object (ws). By converting the data into a JSON string, you can ensure its compatibility and efficient transmission.

const data = { name: 'John', age: 25 };
const serializedData = JSON.stringify(data);

ws.send(serializedData);
Enter fullscreen mode Exit fullscreen mode

The send() method sends the serialized data as a WebSocket message to the connected client.

Receiving and Deserializing Data

When receiving messages from clients, we will need to deserialize the received JSON string to access and process the data.

wss.on('message', (message) => {
  const receivedData = JSON.parse(message);
  // Process the received data
});
Enter fullscreen mode Exit fullscreen mode

Here, the message parameter represents the received WebSocket message. By deserializing the message using JSON.parse(), we can obtain the original data object for further processing.

Advanced features in WebSocket server implementation

WebSocket servers can incorporate advanced features to enhance functionality, security, and scalability. Next, we will explore two important aspects: handling multiple channels and implementing authentication mechanisms.

Handling multiple channels

WebSocket servers often need to handle communication across multiple channels or rooms. Channels allow grouping clients based on their interests, topics, or specific interactions. This enables targeted message broadcasting and efficient data management.

To implement multiple channels in your WebSocket server, you can utilize data structures like dictionaries or arrays to store and manage clients associated with each channel. When a message is received, you can then broadcast it selectively to the clients subscribed to that particular channel.

Here's an example:

const channels = {};

wss.on('connection', (ws) => {
  // Join a specific channel
  const channel = 'general';
  if (!channels[channel]) {
    channels[channel] = [];
  }
  channels[channel].push(ws);

  // Handle incoming messages
  ws.on('message', (message) => {
    // Broadcast message to all clients in the channel
    channels[channel].forEach((client) => {
      client.send(message);
    });
  });

  // Handle disconnections
  ws.on('close', () => {
    // Remove client from the channel
    channels[channel] = channels[channel].filter((client) => client !== ws);
  });
});
Enter fullscreen mode Exit fullscreen mode

In this example, clients joining the WebSocket server are assigned to the 'general' channel by default. However, we can modify the logic to allow clients to specify their desired channel during connection or through specific messages.

Implementing authentication mechanisms

In order to provide secure communication and limit access to authorized clients, WebSocket servers frequently need authentication techniques. Before granting clients access to particular resources or channels, authentication allows for client identification and verification. Before accepting a client connection, your WebSocket server must first validate the client's credentials, such as tokens or session data. We can utilize libraries like JSON Web Tokens (JWT) or integrate with existing authentication solutions (e.g., OAuth) to authenticate clients.

Here's a simplified example demonstrating authentication using JWT:

const jwt = require('jsonwebtoken');

wss.on('connection', (ws, req) => {
  const token = req.headers.authorization.split(' ')[1];

  // Verify and decode JWT token
  jwt.verify(token, 'your_secret_key', (err, decoded) => {
    if (err) {
      // Handle authentication failure
      ws.close();
      return;
    }

    // Authentication successful
    // Handle further operations or channel assignment
  });
});
Enter fullscreen mode Exit fullscreen mode

In this example, the client is expected to send a JWT token in the authorization header. The server verifies the token using a secret key and performs appropriate actions based on the authentication result.

Conclusion

In this article, we explored initializing WebSocket servers, handling WebSocket connections and messages in Node.js, data serialization and deserialization in WebSocket communication, and the implementation of a WebSocket server in Node.js for a real-world use case: real-time stock market data streaming. WebSocket servers offer several advantages for real-time applications, including real-time data updates, reduced network overhead, scalability, and customization.

They enable us to build robust and interactive applications that require instant data streaming and real-time collaboration. By harnessing the power of WebSocket technology, we can create dynamic and responsive applications that deliver real-time data updates and enhance user experiences.

Feel free to contribute or let me know if you have any questions. Let's connect on X!

. . . . . . . . . . . . . . . . . . .