执行node dist/openapi.js生成OpenAPI时NestJS报Connection未找到错误
问题:NestJS生成OpenAPI文档时TypeOrm连接在shutdown阶段报错
NestJS项目在dev/prod模式启动正常,但执行node dist/openapi.js更新openapi.yaml时,抛出Nest无法找到Connection元素的错误,错误栈指向TypeOrmCoreModule的onApplicationShutdown阶段。
项目配置了两个数据库连接:无名称的Oracle连接(GOLD)、名为'reflex'的PostgreSQL连接;ReflexRepository通过@InjectConnection('reflex')注入连接,OmsModule将ReflexRepository声明为provider。
错误信息
2023-06-26T09:45:01+02:00 - OmsModule dependencies initialized (node:10136) UnhandledPromiseRejectionWarning: Error: Nest could not find Connection element (this provider does not exist in the current context) at ContainerScanner.getWrapperCollectionPairByHost (C:\Users\ostorero-gu\Documents\projects\oms-api\node_modules\@nestjs\core\injector\container-sca nner.js:34:19) at ContainerScanner.findInstanceByToken (C:\Users\ostorero-gu\Documents\projects\oms-api\node_modules\@nestjs\core\injector\container-scanner.js:20: 40) at Object.findInstanceByToken (C:\Users\ostorero-gu\Documents\projects\oms-api\node_modules\@nestjs\core\injector\module-ref.js:40:38) at Object.get (C:\Users\ostorero-gu\Documents\projects\oms-api\node_modules\@nestjs\core\injector\module.js:341:29) at TypeOrmCoreModule.<anonymous> (C:\Users\ostorero-gu\Documents\projects\oms-api\node_modules\@nestjs\typeorm\dist\typeorm-core.module.js:94:47) at Generator.next (<anonymous>) at C:\Users\ostorero-gu\Documents\projects\oms-api\node_modules\@nestjs\typeorm\dist\typeorm-core.module.js:20:71 at new Promise (<anonymous>) at __awaiter (C:\Users\ostorero-gu\Documents\projects\oms-api\node_modules\@nestjs\typeorm\dist\typeorm-core.module.js:16:12) at TypeOrmCoreModule.onApplicationShutdown (C:\Users\ostorero-gu\Documents\projects\oms-api\node_modules\@nestjs\typeorm\dist\typeorm-core.module.js :90:16) (node:10136) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2) (node:10136) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will term inate the Node.js process with a non-zero exit code.
相关代码片段
AppModule配置
@Module({ imports: [ LoggerModule, ConfigurationModule, HealthcheckModule, TypeOrmModule.forRootAsync({ imports: [ConfigurationModule], inject: [ConfigurationService], useFactory: async (configurationService: ConfigurationService) => { return ({ type: 'oracle', extra: { connectString: configurationService.get(ConfigurationKeys.GOLDCONNECTSTRING), }, username: configurationService.get(ConfigurationKeys.GOLDUSERNAME), password: configurationService.get(ConfigurationKeys.GOLDPASSWORD), database: '', entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: false, logging: false, }) as TypeOrmModuleOptions; }, }), HealthcheckTypeormModule.register({ name: 'GOLD', type: 'oracle', }), TypeOrmModule.forRootAsync({ imports: [ConfigurationModule], name: 'reflex', inject: [ConfigurationService], useFactory: async (configurationService: ConfigurationService) => { return ({ type: 'postgres', host: configurationService.get(ConfigurationKeys.REFLEXHOST), database: configurationService.get(ConfigurationKeys.REFLEXDB), username: configurationService.get(ConfigurationKeys.REFLEXUSERNAME), password: configurationService.get(ConfigurationKeys.REFLEXPASSWORD), synchronize: false, logging: false, }) as TypeOrmModuleOptions; }, }), OmsModule, ], providers: [] }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer.apply(HelmetMiddleware).forRoutes('*'); } }
ReflexRepository代码
@Injectable() export class ReflexRepository { private static readonly TAG = 'ReflexRepository'; constructor( @InjectConnection('reflex') private readonly connection: Connection, private readonly logger: LoggerService, ) { } public async getOrderPreparation(orderPreparationNumber: string): Promise<Array<ReflexModelOrderPreparation>> { let queryResult; const query = ReflexRepository.getOrderPreparationSql(orderPreparationNumber); try { queryResult = await this.connection.query(query); } catch (e) { this.logger.error(ReflexRepository.TAG, e.message); throw e; } return queryResult; } private static getOrderPreparationSql(orderPreparationNumber: string): string { return `private query`; } async getTrackingByOrderNumber(orderNumber: string): Promise<TrackingCommandeEntity[]> { const result = this.connection.getRepository(TrackingCommandeEntity).find({ NUM_CDE_CLT: orderNumber }); if ((await result).length <= 0) { throw new NotFoundError('Tracking pour la commande introuvable'); } return result; } }
OmsModule配置
@Module({ imports: [ProduitsModule], controllers: [OmsController], providers: [OmsService, GoldRepository, OmsMapper, ReflexRepository], }) export class OmsModule { }
openapi.ts代码
async function run() { const logger = new LoggerService(); const app = await NestFactory.create(AppModule, { logger }); const outputPath = path.resolve(process.cwd(), 'openapi.yaml'); const document = createDocument(app, true); if (!document.definitions) document.definitions = {}; try { writeFileSync(outputPath, toYaml(document, { schema: JSON_SCHEMA }), { encoding: 'utf8' }); } catch (e) { logger.error(`Error while creation yaml openapi : ${e.message}`); } await app.close(); } run();
解决方案
方案1:模拟数据库连接,跳过实际初始化
生成OpenAPI文档无需实际数据库连接,可使用NestApplicationContext并替换Repository为模拟对象,避免连接初始化:
async function run() { const logger = new LoggerService(); const appContext = await NestFactory.createApplicationContext(AppModule, { logger }); // 模拟ReflexRepository的方法,避免依赖实际数据库连接 appContext.select(OmsModule).overrideProvider(ReflexRepository).useValue({ getOrderPreparation: () => Promise.resolve([]), getTrackingByOrderNumber: () => Promise.resolve([]), }); const outputPath = path.resolve(process.cwd(), 'openapi.yaml'); const document = createDocument(appContext, true); if (!document.definitions) document.definitions = {}; try { writeFileSync(outputPath, toYaml(document, { schema: JSON_SCHEMA }), { encoding: 'utf8' }); } catch (e) { logger.error(`Error while creation yaml openapi : ${e.message}`); } await appContext.close(); } run();
方案2:统一Oracle连接的名称配置
主Oracle连接未指定name,默认使用'default',但Healthcheck模块注册的连接name为'GOLD',导致shutdown时连接实例不匹配。修改主连接配置,显式指定name:
TypeOrmModule.forRootAsync({ name: 'GOLD', // 添加该行,与Healthcheck模块的连接名称统一 imports: [ConfigurationModule], inject: [ConfigurationService], useFactory: async (configurationService: ConfigurationService) => { return ({ type: 'oracle', extra: { connectString: configurationService.get(ConfigurationKeys.GOLDCONNECTSTRING), }, username: configurationService.get(ConfigurationKeys.GOLDUSERNAME), password: configurationService.get(ConfigurationKeys.GOLDPASSWORD), database: '', entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: false, logging: false, }) as TypeOrmModuleOptions; }, }),
方案3:手动销毁数据库连接后再关闭应用
在调用app.close()前,手动获取并销毁所有数据库连接,避免Nest在shutdown时找不到连接实例:
async function run() { const logger = new LoggerService(); const app = await NestFactory.create(AppModule, { logger }); const outputPath = path.resolve(process.cwd(), 'openapi.yaml'); const document = createDocument(app, true); if (!document.definitions) document.definitions = {}; try { writeFileSync(outputPath, toYaml(document, { schema: JSON_SCHEMA }), { encoding: 'utf8' }); } catch (e) { logger.error(`Error while creation yaml openapi : ${e.message}`); } // 手动销毁所有数据库连接 const connectionManager = app.get(ConnectionManager); await Promise.all( connectionManager.connections.map(conn => conn.destroy()) ); await app.close(); } run();
内容的提问来源于stack exchange,提问作者Guillaume Ostorero
相关产品推荐
相关产品推荐

