WebSockets on specific route in Nest.js

Viewed 6682

I'd like to create specific API route which will be used only WebSocket (/api/events) but in all examples of implementing WebSockets on Nest.js I stumbled upon module is imported in AppModule and client is emitting events toward the root URL, which I can't do because I have this middleware;

frontend.middleware.ts

import { Request, Response } from 'express';
import { AppModule } from '../../app.module';

export function FrontendMiddleware(
  req: Request,
  res: Response,
  next: Function,
) {
  const { baseUrl } = req;
  if (baseUrl.indexOf('/api') === 0) {
    next();
  } else {
    res.sendFile('index.html', { root: AppModule.getStaticAssetsRootPath() });
  }
}

Here is the EventGateway and EventModule:

event.gateway.ts

import {
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
  WsResponse,
} from '@nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Client, Server } from 'socket.io';

@WebSocketGateway({ namespace: 'events' })
export class EventGateway {
  @WebSocketServer()
  server: Server;

  @SubscribeMessage('events')
  findAll(client: Client, data: any): Observable<WsResponse<number>> {
    return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item })));
  }

  @SubscribeMessage('identity')
  async identity(client: Client, data: number): Promise<number> {
    return data;
  }
}

event.module.ts

import { Module } from '@nestjs/common';
import { EventGateway } from './event.gateway';

@Module({
  components: [EventGateway],
})
export class EventModule {}

Is there a way to create controller which will allow server-client communication via /api/events?

2 Answers

Yes, it is possible to create the WebsocketGateway on another path. You can just use the options of the WebsocketGateway to configure the underlying IO-Connection:

E.g:

import {
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
  WsResponse,
} from '@nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Client, Server } from 'socket.io';

@WebSocketGateway({ path: '/api/events', namespace: 'events' })
export class EventGateway {
  @WebSocketServer()
  server: Server;

  @SubscribeMessage('events')
  findAll(client: Client, data: any): Observable<WsResponse<number>> {
    return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item })));
  }

  @SubscribeMessage('identity')
  async identity(client: Client, data: number): Promise<number> {
    return data;
  }
}

This will start the IO-Connection on http://localhost/api/events

Remember to change the connection-path also in your client. It won't be the default /socket.io path anymore, it will be /api/events in your sample.

Websocket is running on the server, not an endpoint. Therefore you cannot have it listen to requests under a specific route, rather just a port, which for Nest's default configuration happens to be the same as the HTTP one.

You could use a reverse proxy like Nginx to redirect the requests towards /api/events facing the Websocket server and also handle the redirection to index.html without changing even the Websocket server's port. Then you would not need the FrontendMiddleware class at all. It is also better since the application does not take the burden of managing request redirections.

Related