WebSockets with NestJS Mini-Hackathon

The Context

I attended React Native London 67 - Europe’s largest RN meetup, which Theodo organises.
We were building a chat app in React Native, but the thing I was interested in was the WebSocket aspect of real-time messaging without the need to use polling.
We use NestJS as a tech stack at work, which differed from the suggested stack for the hackathon (plain old Express.js as a simpler option) - so I decided to go off-track and see about implementing something simple with NestJS.

The Goal

I wanted to get two separate processes to exchange a message in real-time, via a NestJS server without polling (I want the server to push/ publish the message down to the clients who are listening for updates). This can be done using Websockets.
WebSockets can be better than polling because it requires only a single HTTP request (to open the connection) and you get immediate feedback from the server as soon as something happens (not needing to wait for the poll interval). Of course, the disadvantage (if this is a worry), is you are keeping a connection open for a potentially long amount of time - you couldn’t do this if you planned to deploy it on a lambda or other Serverless/ ephemeral services!

Working Through It

I started with the NestJS docs, which clearly point out that I can do this within its framework.
Other than that, there wasn’t a very extensive example (until the very end of the page), so I googled around for some quick articles to also check for some more complete examples - I wanted to see what others were doing but not copy and paste anything from an example.
To create my repo I ran
npx nest new nest-websocket-test && cd nest-websocket-test
Then to scaffold the project, I need a gateway with a module, so I run:
npx nest g ga events
npx nest g module events
Which does most of the plumbing.
I then update the gateway and add an event handler which will run when WebSocket events are emitted from a client:
// src/events/events.gateway.ts import { ConnectedSocket, MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; @WebSocketGateway() export class EventsGateway { @WebSocketServer() server: Server; // Hooks into the connection event handleConnection(client: any, ...args: any[]) { const { sockets } = this.server.sockets; console.log(`Client id: ${client.id} connected`); console.log(`Number of connected clients: ${sockets.size}`); } // Hooks into the disconnection event handleDisconnect(client: any) { console.log(`Cliend id:${client.id} disconnected`); } @SubscribeMessage('event') handleEvent( @MessageBody() data: string, @ConnectedSocket() client: Socket, ): string { console.log('Handling published event to Socket server'); // broadcast an event to all connections except the publishing socket client.broadcast.emit('event', data); return data; } }
I then created two test clients with ts-node which would be separate processes that could talk to each other via the WebSocket server:
// client/subscriber.ts import { io } from 'socket.io-client'; const socket = io('http://localhost:3000'); console.log('Client 1 connected to websocket'); console.log('Client 1 subscribed to events from server...'); socket.on('event', ({ message }) => { console.log(`Client 1: message received: ${message}`); });
// client/punlisher.ts import { io } from 'socket.io-client'; const socket = io('http://localhost:3000'); console.log('Client 2 connected to websocket'); console.log('Client 2 publishing event to server...'); socket.emit('event', { message: 'Hello Client 1, nice to connect!' }); setTimeout(() => { console.log('Client 2 disconnecting'); socket.disconnect(); }, 1500); // wait a bit before disconnecting

The Result

I run the dev server to start Nest with: pnpm run start:dev
Then I run my script to set up a subscriber: pnpm run client:sub
Then I can run the publisher client as many times as I want: pnpm run client:pub
notion image
We can see logs from the Nest server on the left showing different clients connecting and disconnecting.
In the top right terminal, we see “Client 1” connecting to the server and listening for the “event” events. Running the publisher client script in the bottom right terminal emits a message to the eventHandler on the server - this, in turn, notifies all the other connections (including “Client 1” which is listening) passing on the data that was sent from Client 2.
This is cool because the standard web architecture with HTTP requests implies that we always have HTTP requests initiated by the client, however here we see the server “pushing” down to the client in the reverse direction to the conventional request!

The Code

Find the full code with reproduction steps in the git commit history on GitHub here!