9

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.

5 Answers 5

15

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

Sign up to request clarification or add additional context in comments.

1 Comment

This is neat. Thanks you :)
4

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.

Comments

0
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

Comments

0

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)

Comments

-1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.