Nest could not find Sequelize element when calling `app.close()` - node.js

We have a web server running Nest 8.0.8. With the given module setup:
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
cache: true,
validate,
load: [configuration]
}),
SequelizeModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => config.get('db1'),
inject: [ConfigService]
}),
SequelizeModule.forRootAsync({
name: 'db2',
imports: [ConfigModule],
useFactory: (config: ConfigService) => config.get('db2'),
inject: [ConfigService],
}),
controllers: [...],
providers: [...]
})
To start the application, we have this main.ts file:
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(...)
);
useContainer(app.select(AppModule), { fallbackOnErrors: true });
app.useGlobalPipes(
new ValidationPipe({
exceptionFactory: (errors) => new ValidationException(errors),
transform: true,
whitelist: true,
validationError: {
target: false,
value: false
}
})
);
app.setGlobalPrefix('v1');
app.useWebSocketAdapter(new RedisIoAdapter(app));
app.enableCors({ origin: '*' });
/**
* When SIGTERM, run functions before shutting down
*/
app.enableShutdownHooks();
if (process.env.SETUP) {
const service = app.get(FooService);
await service.setup();
await app.close();
return;
}
await app.listen(3000, '0.0.0.0');
}
bootstrap().then();
When running out unit tests, we use Nest to automatically configure the database. Initially after initializing the server with the command SETUP=true nest start that will force Nest to not listen the port and early return after running service.setup(). (note that this problem also happens when calling the app.listen(...) and app.close() functions)
The problem happens at the call app.close() we expect Nest to close all connections, including the two SequelizeModule of the 2 databases connected.
Unfortunaly Nest closes the first connection and tries to close the second, but the module (we suspect) is already closed. Giving the error:
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(). The promise rejected with the reason:
Error: Nest could not find Sequelize element (this provider does not exist in the current context)
at InstanceLinksHost.get (/Users/me/Repository/project_nest/node_modules/#nestjs/core/injector/instance-links-host.js:21:19)
at Object.find (/Users/me/Repository/project_nest/node_modules/#nestjs/core/injector/module-ref.js:38:55)
at Object.get (/Users/me/Repository/project_nest/node_modules/#nestjs/core/injector/module.js:345:28)
at SequelizeCoreModule.<anonymous> (/Users/me/Repository/project_nest/node_modules/#nestjs/sequelize/dist/sequelize-core.module.js:81:47)
at Generator.next (<anonymous>)
at /Users/me/Repository/project_nest/node_modules/#nestjs/sequelize/dist/sequelize-core.module.js:20:71
at new Promise (<anonymous>)
at __awaiter (/Users/me/Repository/project_nest/node_modules/#nestjs/sequelize/dist/sequelize-core.module.js:16:12)
at SequelizeCoreModule.onApplicationShutdown (/Users/me/Repository/project_nest/node_modules/#nestjs/sequelize/dist/sequelize-core.module.js:80:16)
at Object.callAppShutdownHook (/Users/me/Repository/project_nest/node_modules/#nestjs/core/hooks/on-app-shutdown.hook.js:51:35)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at NestApplication.callShutdownHook (/Users/me/Repository/project_nest/node_modules/#nestjs/core/nest-application-context.js:199:13)
at NestApplication.close (/Users/me/Repository/project_nest/node_modules/#nestjs/core/nest-application-context.js:83:9)
at bootstrap (/Users/me/Repository/project_nest/src/main.ts:143:5)
We initially thing that is a problem on Nest but we are not sure. This used to work in the past but somehow after a update the issue started happening and we cannot downgrade the library due some constraints with other dependencies.
I would like to know what can be done to either debug or sort the issue, we tried to debug the application but nothing points out to issues in our codebase. Could you please help?
Regards, Pedro

you need to give name for you second configuration SequelizeModule.
Example for your situation
SequelizeModule.forRootAsync({
name: 'db2',
imports: [ConfigModule],
useFactory: (config: ConfigService) => {
name: 'db2',
...config.get('db2')
},
inject: [ConfigService],
}),
The reason of this problem is getConnectionToken function which uses for getting connection by name from options. And if you not use specific name, SequelizeModule will use one connection between two modules and will close them both

Related

NestJS-Telegraf Telegram bot only handles one request at a time

I started building a simple telegram bot using nestjs-telegraf package.
Following the docs, I created the basic AppUpdate service:
#Update()
#Injectable()
export class AppUpdate {
#Command('random')
async onRandom(#Ctx() ctx: Context) {
const resp = await asyncRequestToGetData(ctx.message.text);
await ctx.reply(resp);
}
}
And provided it like this in app.module
#Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TelegrafModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
token: configService.get<string>('TELEGRAM_BOT_TOKEN'),
}),
inject: [ConfigService],
}),
HttpModule,
],
controllers: [AppController],
providers: [AppUpdate, ...],
})
export class AppModule {}
The asyncRequestToGetData is an http call that sometimes takes 10 seconds or more.
And for some reason if another user tries to call the same command on the bot while request is processing, the onRandom is not called until the processing is finished.
Basically, not able to handle concurrent requests.
Was not able to find the reason/solution so far.

How to establish database connection within a Nest.js e2e test

I'm having an issue connecting to CosmosDB within a Nest.js e2e test.
I've been following the Nest.js documentation for creating e2e tests but it doesn't contain any documentation on connecting to a test database. Some of the articles I found (both on SO, and numerous other websites) stated that I should just initialize the database as I would within a regular module, but inside of the testing module, but it didn't work for me.
Module for CosmosDB that I'm using is #nestjs/azure-database.
I tried to initialize the AzureCosmosDbModule through the imports of the testing module as mentioned above but without any success.
Below is a snippet of what I tried. (I changed the actual name of the controller, module, etc. to a random "cats" one)
Environment variable loading is set up correctly, as it works outside of the tests.
describe('CatsController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
validationSchema: schema,
isGlobal: true,
}),
AzureCosmosDbModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (cfg: ConfigService) => ({
endpoint: cfg.get<string>('AZURE_COSMOS_DB_ENDPOINT'),
dbName: cfg.get<string>('AZURE_COSMOS_DB_TEST_NAME'),
key: cfg.get<string>('AZURE_COSMOS_DB_KEY'),
}),
inject: [ConfigService],
}),
CatsModule,
],
}).compile();
app = moduleFixture.createNestApplication();
app.setGlobalPrefix('api');
await app.init();
});
afterAll(async () => {
await app.close();
});
});
Error message I get after running e2e test.
[Nest] 2355 - 08/25/2022, 11:27:33 AM ERROR [AzureCosmosDbModule] Unable to connect to the cosmos db database. Retrying (1)...
[Nest] 2355 - 08/25/2022, 11:27:36 AM ERROR [AzureCosmosDbModule] Unable to connect to the cosmos db database. Retrying (2)...

NestJs with Fastify doesn't execute code after app.listen()

this is my first question here, so i'd like to apologize in advance if i miss something important.
So this is pretty basic, but i didn't find an answer anywhere, as the title states, my NestJs application running with Fastify, simply does not execute any code after the app.listen('port') line, this doesn't happen with Express. OBS: I'm referring to the main.ts file.
relevant code below:
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({ logger: { level: 'warn' } }),
);
const configService = app.get(ConfigService);
await app.listen(configService.get<number>(ENV_KEYS.PORT), '0.0.0.0');
console.log(
`Application is running on: ${await app.getUrl()}\nApplication Version: ${version}`,
);
}
bootstrap();
The console.log() after await app.listen never executes, even if the app is working normally, and as far as my tests show, no code after await app.listen() ever executes.
I'd like to know how to overcome this issue, because i need to run some code after the app is already bootstrapped.
So thanks to #Micael Levi pointing me to an issue on github (github.com/nestjs/nest/issues/7572) i started to look into problems within my controllers, and the reason to freeze the application on app.listen() was because of "an double instance" of my AuthController, let me explain
I had my AuthController defined on AuthModule:
// auth.module.ts
#Module({
imports: [
PassportModule,
JwtModule.register({
secret: JWT_CONSTANTS.SECRET,
}),
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService, LocalStrategy, JwtStrategy],
})
And in the AppModule, i was importing AuthModule while also declaring AuthController AGAIN:
// app.module.ts
#Module({
imports: [
ConfigModule.forRoot(getConfigServiceConfiguration()),
TypeOrmModule.forRootAsync({
useFactory: async (configService: ConfigService) =>
getDatabaseModuleOptionsAsync(configService),
inject: [ConfigService],
}),
AuthModule, // <-AuthController is declared within AuthModule scope
UsuarioModule,
ClienteModule,
],
controllers: [AppController, AuthController] // <- AuthController here again,
providers: [AppService],
})
Removing AuthController from controllers:[] in AppModule solved my problem. Rookie mistake, but for some reason this isn't a problem with express, and doesn't raise any compilation errors with Fastify either!

NestJS, using typeorm on providers

I want to make a provider which queries an entity from db. (what interests me actually is to make typeorm available when I create a provider).
I get
[ExceptionHandler] Connection "default" was not found. - {"trace":"ConnectionNotFoundError: Connection \"default\" was not found.\n
I tried using service, using custom repository, nothing works. I have something like this in module:
{
provide: MICROSERVICE_ID,
useFactory: async (): Promise<Microservice> => {
//ignore logic
return await getRepository(Microservice).findOne(1);
},
inject: []
}
TypeOrm is imported on app.module.ts
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => (configService.get('database'))
}),
Any help is appreciated.
Check your database file. The Entity may not load as expected.
For newest NestJS version. We should load from ./dist/
Fixed by injecting Connection from typeorm.
import { getRepository, Connection } from 'typeorm';
{
provide: MICROSERVICE_ID,
useFactory: async (): Promise<Microservice> => {
//ignore logic
return await getRepository(Microservice).findOne(1);
},
inject: [Connection]
}

Multiple databases from an asynchronous config service?

NestJS documentation describes how to define multiple databases:
https://docs.nestjs.com/techniques/database#multiple-databases
As well as using a database config service for asynchronous configuration:
https://docs.nestjs.com/techniques/database#async-configuration
Is it possible to define multiple databases from an async config service?
Experimentally I tried returning an array of options from my service:
class TypeOrmConfigService implements TypeOrmOptionsFactory {
createTypeOrmOptions() {
return [
{ ... },
{ ... }
];
}
}
but no luck with this approach.
It looks like for each connection you want to have you will need to create a separate configuration instance. What I might suggest would be some sort of factory that takes in a name, and maybe the config key for the database connection, then using those in the factory to help return the expected values. So the docs have a factory like
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
type: 'mysql',
host: configService.getString('HOST'),
port: configService.getString('PORT'),
username: configService.getString('USERNAME'),
password: configService.getString('PASSWORD'),
database: configService.getString('DATABASE'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
inject: [ConfigService],
});
You could create a similar factory along the lines of
const typeOrmFactory = (
configName: string,
entities: string[],
databaseName?: string
) => (configService: ConfigService): TypeOrmModuleOptions => ({
type: 'postgres', // whatever you use
url: configService.get(configName), // dynamically change which connection you're working with based on the factory input
name: databaseName || 'default',
synchronize: true,
entities // path to the entity/entities you are working with
});
Now in your app.module you can use the factory like so
#Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: typeOrmFactory('DATABASE_URL', ['some/path.ts']),
inject: [ConfigService]
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: typeOrmFactory('DATABASE_URL_2', ['some/other/path.ts'], 'secondaryDatabase'),
inject: [ConfigServce]
],
})
export class AppModule {}
Make sure you tell Typescript that you are returning the type TypeOrmModuleOptions otherwise it tells you that the functions are not compatible. You can also save most of the configs in the config service and environment variables if you would like.

Resources