Nowa Concordia
Nowa Concordia

Reputation: 739

How to pass a dynamic port to the Websockets-gateway in NestJS?

I wanted to dynamically set the Websockets-gateway port from config in NestJS. Below is my websockets-gateway code.

import { WebSocketGateway } from '@nestjs/websockets';

const WS_PORT = parseInt(process.env.WS_PORT);

@WebSocketGateway(WS_PORT)
export class WsGateway {
  constructor() {
    console.log(WS_PORT);
  }
}

But the WS_PORT is always NaN.

This is my bootstrap function insdie main.ts :

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { cors: false });
  const configService = app.get(ConfigService);
  initAdapters(app);
  await app.listen(configService.get(HTTP_PORT), () => {
    console.log('Listening on port ' + configService.get(HTTP_PORT));
  });
}

Below is my app.module.ts :

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: './src/config/dev.env',
      isGlobal: true,
    }),
    RedisModule,
    SocketStateModule,
    RedisPropagatorModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        secret: configService.get<string>(JWT_SECRET_KEY),
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [AppController],
  providers: [WsGateway, AppService],
})
export class AppModule {}

I put a console log in the Gateway constructor to print the value of 'WS_PORT' but it's always NaN.

[Nest] 13252  - 10/04/2021, 5:05:34 PM     LOG [NestFactory] Starting Nest application...
NaN

Thanks in advance.

Upvotes: 9

Views: 6762

Answers (5)

Marcin Wanago
Marcin Wanago

Reputation: 653

You can extend the existing IoAdapter.

import { IoAdapter } from '@nestjs/platform-socket.io';
import { INestApplicationContext } from '@nestjs/common';
import { Server, ServerOptions } from 'socket.io';
import { CorsOptions } from 'cors';

export class Adapter extends IoAdapter {
  constructor(
    appOrHttpServer: INestApplicationContext,
    private readonly corsOptions: CorsOptions,
  ) {
    super(appOrHttpServer);
  }

  create(port: number, options?: ServerOptions): Server {
    return super.create(port, {
      ...options,
      cors: this.corsOptions,
    });
  }
}

Then, you need to use it in the bootstrap function.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
import { Adapter } from './chat/adapter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const configService = app.get(ConfigService);

  app.enableCors({
    origin: configService.get('FRONTEND_URL'),
  });

  app.useWebSocketAdapter(
    new Adapter(app, {
      origin: configService.get('FRONTEND_URL'),
    }),
  );

  await app.listen(3000);
}
bootstrap();

In my case, the FRONTEND_URL variable is the URL of my frontend application.

FRONTEND_URL=http://localhost:5173

Upvotes: 0

DLara
DLara

Reputation: 329

You can use the dotenv package(used by @nestjs/config module) and then import your config file in the websocket module

config/env.ts:

import * as dotenv from 'dotenv';
dotenv.config();

export default () => {
  return {
    wsPort: parseInt(process.env.WS_PORT),
  }
}

events.gateway.ts:

import envSetup from '../config/env';
@WebSocketGateway(envSetup.wsPort)

Upvotes: 0

Robin De Schepper
Robin De Schepper

Reputation: 6404

You can do it relatively straightforward if you decorate the Gateway before app.init is called:

  1. Import the class in main.ts
  2. Get an instance of your ConfigurationService
  3. Manually call the decorator on the class with the config data
function decorateGateway(class_, config) {
  // Just calling the decorator as a function with the class
  // as argument does the same as `@WebSocketGateway`
  WebSocketGateway({
    cors: {
      origin: config.get("websocket.cors.origin"),
    }
  })(class_)
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {});
  const config = app.get(ConfigService);
  decorateGateway(ChatGateway, config);
  ...
  app.init();
}

The tricky part with a Gateway is that it starts up together with the server, and the decorator metadata needs to be applied to the class earlier than for other components. You can do this in main.ts before app.init.

Upvotes: 4

Aleksey Kolyasa
Aleksey Kolyasa

Reputation: 81

port = this.configService.get<number>('SOCKETIO.SERVER.PORT');

In my case I found its return string(from .env), port gets 'string' but not 'number',

but if put parseInt(this.configService.get<number>('SOCKETIO.SERVER.PORT'), 10); then it is ok

Mind that socket-io ports must be the same on server & client side

Upvotes: 0

I could not find a way to add dynamic data to the decorator. So to be able to dynamically choose the port and other configurations I had to:

  • Create an adapter for socket-io:
  • Tell NestJs to use the new adapter

SocketIoAdapter.ts

import { INestApplicationContext } from '@nestjs/common';
import { IoAdapter } from '@nestjs/platform-socket.io';
import { ServerOptions } from 'socket.io';
import { ConfigService } from '@nestjs/config';

export class SocketIoAdapter extends IoAdapter {
constructor(
  private app: INestApplicationContext,
  private configService: ConfigService,
) {
  super(app);
}

createIOServer(port: number, options?: ServerOptions) {
  port = this.configService.get<number>('SOCKETIO.SERVER.PORT');
  const path = this.configService.get<string>('SOCKETIO.SERVER.PATH');
  const origins = this.configService.get<string>(
    'SOCKETIO.SERVER.CORS.ORIGIN',
  );
  const origin = origins.split(',');
  options.path = path;
  options.cors = { origin };
  const server = super.createIOServer(port, options);
  return server;
}
}

Now, you need to edit the main.ts to use the adapter

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
import { SocketIoAdapter } from './socket-io/socket-io.adapter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);
  const hosts = configService.get<string>('CORS.HOST');
  const hostsArray = hosts.split(',');
  app.enableCors({
    origin: hostsArray,
    credentials: true,
  });
//Here you use the adapter and sent the config service
  app.useWebSocketAdapter(new SocketIoAdapter(app, configService));
  await app.listen(4300);
}
bootstrap();


In this case I set the port and the cors origin, here an example of the conf file (using .env) env.local

SOCKETIO.SERVER.PORT=4101
SOCKETIO.SERVER.PATH=
SOCKETIO.SERVER.CORS.ORIGIN=http://localhost:4200,http://localhost.com:8080

Here a link to config service Config Service NestJs

Upvotes: 15

Related Questions