Real-time Applications with NestJS and WebSockets
Tags: nestjs, backend, typescript, webdev
Published: 2024-06-10 19:10:48

Real-time applications have become an integral part of modern web development, enabling instant communication and dynamic interactions. NestJS, with its powerful framework and support for WebSockets, makes it easy to build such applications. In this article, we'll explore how to create real-time applications with NestJS and WebSockets, covering key concepts, implementation steps, and best practices.

**Why Use WebSockets?**

WebSockets provide a full-duplex communication channel over a single TCP connection, allowing real-time data exchange between the client and server. Unlike traditional HTTP requests, WebSockets maintain an open connection, enabling instant updates without the need for polling.

**Setting Up a NestJS Project**

First, create a new NestJS project:

```
nest new real-time-app
```

Navigate to the project directory:

```
cd real-time-app
```

**Installing WebSocket Dependencies**

NestJS provides a WebSocket module out of the box. Install the necessary package:

```
npm install @nestjs/websockets @nestjs/platform-socket.io
```

**Creating a WebSocket Gateway**

A gateway in NestJS acts as a controller for WebSocket events. Create a new gateway using the NestJS CLI:

```
nest generate gateway chat
```

This will create a new chat.gateway.ts file. Let's define our gateway to handle real-time chat messages:

```
import {
  SubscribeMessage,
  WebSocketGateway,
  OnGatewayInit,
  OnGatewayConnection,
  OnGatewayDisconnect,
  WebSocketServer,
} from '@nestjs/websockets';
import { Logger } from '@nestjs/common';
import { Socket, Server } from 'socket.io';

@WebSocketGateway()
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer() server: Server;
  private logger: Logger = new Logger('ChatGateway');

  @SubscribeMessage('message')
  handleMessage(client: Socket, payload: string): void {
    this.server.emit('message', payload);
  }

  afterInit(server: Server) {
    this.logger.log('Init');
  }

  handleConnection(client: Socket, ...args: any[]) {
    this.logger.log(`Client connected: ${client.id}`);
  }

  handleDisconnect(client: Socket) {
    this.logger.log(`Client disconnected: ${client.id}`);
  }
}

```

**Explanation**

- @WebSocketGateway(): Decorator to mark the class as a WebSocket gateway
- @WebSocketServer(): Decorator to inject the WebSocket server instance
- handleMessage(): Method to handle incoming messages and broadcast them to all connected clients
- afterInit(): Lifecycle hook called after the gateway is initialized
- handleConnection(): Lifecycle hook called when a client connects
- handleDisconnect(): Lifecycle hook called when a client disconnects

**Handling Events on the Client-Side**

To handle WebSocket events on the client side, you can use the socket.io-client library. Install it using:

```
npm install socket.io-client
```

Here’s an example of a simple client-side implementation in an HTML file:

```
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Chat App</title>
  <script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const socket = io('http://localhost:3000');

      const form = document.getElementById('form');
      const input = document.getElementById('input');
      const messages = document.getElementById('messages');

      form.addEventListener('submit', (e) => {
        e.preventDefault();
        if (input.value) {
          socket.emit('message', input.value);
          input.value = '';
        }
      });

      socket.on('message', (message) => {
        const item = document.createElement('li');
        item.textContent = message;
        messages.appendChild(item);
      });
    });
  </script>
</head>
<body>
  <ul id="messages"></ul>
  <form id="form" action="">
    <input id="input" autocomplete="off" /><button>Send</button>
  </form>
</body>
</html>
```

**Explanation**

- Socket.IO: The socket.io-client library is used to handle WebSocket connections
- Connecting to the Server: io('http://localhost:3000') establishes a connection to the WebSocket server
- Sending Messages: The form submission event sends messages to the server using socket.emit('message', input.value)
- Receiving Messages: The socket.on('message') event handler updates the DOM with new messages


**Best Practices**

1. Authentication and Authorization: Ensure that WebSocket connections are authenticated and authorized. Use middlewares or guards to secure your WebSocket endpoints

2. Error Handling: Implement proper error handling for your WebSocket events to ensure robustness

3. Scalability: Use tools like Redis for pub/sub patterns if you need to scale your WebSocket server

4. Monitoring and Logging: Integrate monitoring tools to keep track of WebSocket connections and events

**Conclusion**

Building real-time applications with NestJS and WebSockets is straightforward and efficient. By leveraging NestJS's WebSocket module, you can create highly interactive and responsive applications. Follow best practices for authentication, error handling, and scalability to ensure your real-time application is robust and secure.

**My way is not the only way!**