diff --git a/integration/nest-application/listen/e2e/express.spec.ts b/integration/nest-application/listen/e2e/express.spec.ts new file mode 100644 index 00000000000..98e7573683d --- /dev/null +++ b/integration/nest-application/listen/e2e/express.spec.ts @@ -0,0 +1,47 @@ +import { ExpressAdapter } from '@nestjs/platform-express'; +import { Test, TestingModule } from '@nestjs/testing'; +import { expect } from 'chai'; +import * as express from 'express'; +import { AppModule } from '../src/app.module'; +import { INestApplication } from '@nestjs/common'; + +describe('Listen (Express Application)', () => { + let testModule: TestingModule; + let app: INestApplication; + + beforeEach(async () => { + testModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + app = testModule.createNestApplication(new ExpressAdapter(express())); + }); + + afterEach(async () => { + app.close(); + }); + + it('should resolve with httpServer on success', async () => { + const response = await app.listen(3000); + expect(response).to.eql(app.getHttpServer()); + }); + + it('should reject if the port is not available', async () => { + await app.listen(3000); + const secondApp = testModule.createNestApplication( + new ExpressAdapter(express()), + ); + try { + await secondApp.listen(3000); + } catch (error) { + expect(error.code).to.equal('EADDRINUSE'); + } + }); + + it('should reject if there is an invalid host', async () => { + try { + await app.listen(3000, '1'); + } catch (error) { + expect(error.code).to.equal('EADDRNOTAVAIL'); + } + }); +}); diff --git a/integration/nest-application/listen/e2e/fastify.spec.ts b/integration/nest-application/listen/e2e/fastify.spec.ts new file mode 100644 index 00000000000..9d948d39b4b --- /dev/null +++ b/integration/nest-application/listen/e2e/fastify.spec.ts @@ -0,0 +1,46 @@ +import { FastifyAdapter } from '@nestjs/platform-fastify'; +import { Test, TestingModule } from '@nestjs/testing'; +import { expect } from 'chai'; +import { AppModule } from '../src/app.module'; +import { INestApplication } from '@nestjs/common'; + +describe('Listen (Fastify Application)', () => { + let testModule: TestingModule; + let app: INestApplication; + + beforeEach(async () => { + testModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + app = testModule.createNestApplication(new FastifyAdapter()); + }); + + afterEach(async () => { + app.close(); + }); + + it('should resolve with httpServer on success', async () => { + const response = await app.listen(3000); + expect(response).to.eql(app.getHttpServer()); + }); + + it('should reject if the port is not available', async () => { + await app.listen(3000); + const secondApp = testModule.createNestApplication(new FastifyAdapter()); + try { + await secondApp.listen(3000); + } catch (error) { + expect(error.code).to.equal('EADDRINUSE'); + } + + await secondApp.close(); + }); + + it('should reject if there is an invalid host', async () => { + try { + await app.listen(3000, '1'); + } catch (error) { + expect(error.code).to.equal('EADDRNOTAVAIL'); + } + }); +}); diff --git a/integration/nest-application/listen/src/app.controller.ts b/integration/nest-application/listen/src/app.controller.ts new file mode 100644 index 00000000000..8d4f6eb02ce --- /dev/null +++ b/integration/nest-application/listen/src/app.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + getHello(): string { + return this.appService.sayHello(); + } +} diff --git a/integration/nest-application/listen/src/app.module.ts b/integration/nest-application/listen/src/app.module.ts new file mode 100644 index 00000000000..7845d045f8d --- /dev/null +++ b/integration/nest-application/listen/src/app.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/integration/nest-application/listen/src/app.service.ts b/integration/nest-application/listen/src/app.service.ts new file mode 100644 index 00000000000..efeb6b7734d --- /dev/null +++ b/integration/nest-application/listen/src/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + sayHello(): string { + return 'Hello World!'; + } +} diff --git a/integration/websockets/e2e/error-gateway.spec.ts b/integration/websockets/e2e/error-gateway.spec.ts index e48d6ca0c17..57b73be0d8a 100644 --- a/integration/websockets/e2e/error-gateway.spec.ts +++ b/integration/websockets/e2e/error-gateway.spec.ts @@ -12,7 +12,7 @@ describe('ErrorGateway', () => { providers: [ErrorGateway], }).compile(); app = await testingModule.createNestApplication(); - await app.listenAsync(3000); + await app.listen(3000); }); it(`should handle error`, async () => { diff --git a/integration/websockets/e2e/gateway-ack.spec.ts b/integration/websockets/e2e/gateway-ack.spec.ts index 416ba128ae9..e0138349340 100644 --- a/integration/websockets/e2e/gateway-ack.spec.ts +++ b/integration/websockets/e2e/gateway-ack.spec.ts @@ -17,7 +17,7 @@ describe('WebSocketGateway (ack)', () => { it(`should handle message with ack (http)`, async () => { app = await createNestApp(AckGateway); - await app.listenAsync(3000); + await app.listen(3000); ws = io.connect('http://localhost:8080'); await new Promise(resolve => @@ -30,7 +30,7 @@ describe('WebSocketGateway (ack)', () => { it(`should handle message with ack & without data (http)`, async () => { app = await createNestApp(AckGateway); - await app.listenAsync(3000); + await app.listen(3000); ws = io.connect('http://localhost:8080'); await new Promise(resolve => diff --git a/integration/websockets/e2e/gateway.spec.ts b/integration/websockets/e2e/gateway.spec.ts index f840788fc91..c1b004828a7 100644 --- a/integration/websockets/e2e/gateway.spec.ts +++ b/integration/websockets/e2e/gateway.spec.ts @@ -19,7 +19,7 @@ describe('WebSocketGateway', () => { it(`should handle message (2nd port)`, async () => { app = await createNestApp(ApplicationGateway); - await app.listenAsync(3000); + await app.listen(3000); ws = io.connect('http://localhost:8080'); ws.emit('push', { @@ -35,7 +35,7 @@ describe('WebSocketGateway', () => { it(`should handle message (http)`, async () => { app = await createNestApp(ServerGateway); - await app.listenAsync(3000); + await app.listen(3000); ws = io.connect('http://localhost:3000'); ws.emit('push', { @@ -51,7 +51,7 @@ describe('WebSocketGateway', () => { it(`should handle message (2 gateways)`, async () => { app = await createNestApp(ApplicationGateway, NamespaceGateway); - await app.listenAsync(3000); + await app.listen(3000); ws = io.connect('http://localhost:8080'); io.connect('http://localhost:8080/test').emit('push', {}); diff --git a/integration/websockets/e2e/ws-gateway.spec.ts b/integration/websockets/e2e/ws-gateway.spec.ts index e96b05150af..4abca75a5a7 100644 --- a/integration/websockets/e2e/ws-gateway.spec.ts +++ b/integration/websockets/e2e/ws-gateway.spec.ts @@ -21,7 +21,7 @@ describe('WebSocketGateway (WsAdapter)', () => { it(`should handle message (2nd port)`, async () => { app = await createNestApp(ApplicationGateway); - await app.listenAsync(3000); + await app.listen(3000); ws = new WebSocket('ws://localhost:8080'); await new Promise(resolve => ws.on('open', resolve)); @@ -44,7 +44,7 @@ describe('WebSocketGateway (WsAdapter)', () => { it(`should handle message (http)`, async () => { app = await createNestApp(ServerGateway); - await app.listenAsync(3000); + await app.listen(3000); ws = new WebSocket('ws://localhost:3000'); await new Promise(resolve => ws.on('open', resolve)); @@ -69,7 +69,7 @@ describe('WebSocketGateway (WsAdapter)', () => { this.retries(10); app = await createNestApp(ApplicationGateway, CoreGateway); - await app.listenAsync(3000); + await app.listen(3000); // open websockets delay await new Promise(resolve => setTimeout(resolve, 1000)); diff --git a/packages/common/interfaces/nest-application.interface.ts b/packages/common/interfaces/nest-application.interface.ts index 865977f3218..d6f48c93d6f 100644 --- a/packages/common/interfaces/nest-application.interface.ts +++ b/packages/common/interfaces/nest-application.interface.ts @@ -50,15 +50,9 @@ export interface INestApplication extends INestApplicationContext { callback?: () => void, ): Promise; - /** - * Returns the url the application is listening at, based on OS and IP version. Returns as an IP value either in IPv6 or IPv4 - * - * @returns {Promise} The IP where the server is listening - */ - getUrl(): Promise; - /** * Starts the application (can be awaited). + * @deprecated use "listen" instead. * * @param {number|string} port * @param {string} [hostname] @@ -66,6 +60,13 @@ export interface INestApplication extends INestApplicationContext { */ listenAsync(port: number | string, hostname?: string): Promise; + /** + * Returns the url the application is listening at, based on OS and IP version. Returns as an IP value either in IPv6 or IPv4 + * + * @returns {Promise} The IP where the server is listening + */ + getUrl(): Promise; + /** * Registers a prefix for every HTTP route path. * diff --git a/packages/core/nest-application.ts b/packages/core/nest-application.ts index aca85e74dfa..49b9b7b1270 100644 --- a/packages/core/nest-application.ts +++ b/packages/core/nest-application.ts @@ -235,58 +235,69 @@ export class NestApplication this.httpAdapter.enableCors(options); } - public async listen( - port: number | string, - callback?: () => void, - ): Promise; - public async listen( - port: number | string, - hostname: string, - callback?: () => void, - ): Promise; + public async listen(port: number | string): Promise; + public async listen(port: number | string, hostname: string): Promise; public async listen(port: number | string, ...args: any[]): Promise { !this.isInitialized && (await this.init()); - this.isListening = true; - this.httpAdapter.listen(port, ...args); - return this.httpServer; - } - public listenAsync(port: number | string, hostname?: string): Promise { - return new Promise(resolve => { - const server: any = this.listen(port, hostname, () => resolve(server)); + return new Promise((resolve, reject) => { + const errorHandler = (e: any) => { + this.logger.error(e?.toString?.()); + reject(e); + }; + this.httpServer.once('error', errorHandler); + + this.httpAdapter.listen(port, ...args, () => { + const address = this.httpServer.address(); + if (address) { + this.httpServer.removeListener('error', errorHandler); + this.isListening = true; + resolve(this.httpServer); + } + }); }); } + public listenAsync(port: number | string, ...args: any[]): Promise { + this.logger.warn( + 'DEPRECATED! "listenAsync" method is deprecated and will be removed in the next major release. Please, use "listen" instead.', + ); + return this.listen(port, ...(args as [any])); + } + public async getUrl(): Promise { return new Promise((resolve, reject) => { if (!this.isListening) { this.logger.error(MESSAGES.CALL_LISTEN_FIRST); reject(MESSAGES.CALL_LISTEN_FIRST); } - this.httpServer.on('listening', () => { - const address = this.httpServer.address(); - if (typeof address === 'string') { - if (platform() === 'win32') { - return address; - } - const basePath = encodeURIComponent(address); - return `${this.getProtocol()}+unix://${basePath}`; - } - let host = this.host(); - if (address && address.family === 'IPv6') { - if (host === '::') { - host = '[::1]'; - } else { - host = `[${host}]`; - } - } else if (host === '0.0.0.0') { - host = '127.0.0.1'; - } - resolve(`${this.getProtocol()}://${host}:${address.port}`); - }); + const address = this.httpServer.address(); + resolve(this.formatAddress(address)); }); } + private formatAddress(address: any): string { + if (typeof address === 'string') { + if (platform() === 'win32') { + return address; + } + const basePath = encodeURIComponent(address); + return `${this.getProtocol()}+unix://${basePath}`; + } + let host = this.host(); + if (address && address.family === 'IPv6') { + if (host === '::') { + host = '[::1]'; + } else { + host = `[${host}]`; + } + } else if (host === '0.0.0.0') { + host = '127.0.0.1'; + } + + return `${this.getProtocol()}://${host}:${address.port}`; + } + public setGlobalPrefix(prefix: string): this { this.config.setGlobalPrefix(prefix); return this;