I made two microservice with nest.js and I connected these services on gateway. There is no problem while I work with localhost but I can't receive a response from host. This is the error:
Error: Connection closed
at ClientTCP.handleClose (/Users/ali/Desktop/topstudy/topstudy-app/node_modules/#nestjs/microservices/client/client-tcp.js:79:25)
at Socket.<anonymous> (/Users/ali/Desktop/topstudy/topstudy-app/node_modules/#nestjs/microservices/client/client-tcp.js:69:55)
at Socket.emit (node:events:532:35)
at TCP.<anonymous> (node:net:687:12)
And this is my port and host values:
{
imports: [ConfigModule],
inject: [ConfigService],
name: 'SCHOOL_SERVICE',
useFactory: (configService: ConfigService) => {
return {
transport: Transport.TCP,
options: {
host: 'url.herokuapp.com',
port: 80,
},
}
},
}
If I switch this credentials with the local ones works as expected. But I'm sure host credentials are not wrong I get connection closed error after 30 seconds.
Related
I deployed a NodeJS application to Heroku and attach to it a Postgres database with Heroku Postgres addons, all was working perfectly.
After a few hours, I was unable to authenticate my application. After checking the logs, I understand that the application is unable to connect to the database because of a wrong password.
After that, I also got 2 emails from Heroku that alert me of the maintenance.
So I checked the credentials database and the credentials are all the same except the URI.
I also checked on my env vars and I saw that Heroku added 2 more variables HEROKU_DATABASE_URL and HEROKU_POSTGRESQL_CRIMSON_URL, I tried to edit them with the new URI of the updated Database but do have permission ...
I also tried to delete and recreate a new database but every time the Heroku Maintenance come back and break my app
Here is my connection code to Postgres (using nest js)
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
const isProduction = configService.get('STAGE') === 'prod';
return {
ssl: isProduction,
extra: {
ssl: isProduction ? { rejectUnauthorized: false } : null,
},
type: 'postgres',
autoLoadEntities: true,
synchronize: true,
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
username: configService.get('DB_USERNAME'),
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_DATABASE'),
};
},
}),
Here is the error i get
ERROR [ExceptionHandler] password authentication failed for user
"ryooypezxslwmn"
Heroku Postgres provides and maintains one environment variable per provisioned database:
As part of the provisioning process, a DATABASE_URL config var is added to your app’s configuration. DATABASE_URL contains the URL your app uses to access the database. If your app already has a Heroku Postgres database and you’ve provisioned another one, this config var’s name instead has the format HEROKU_POSTGRESQL_<COLOR>_URL (for example, HEROKU_POSTGRESQL_YELLOW_URL).
When your database connection string needs to change, Heroku automatically updates the DATABASE_URL and HEROKU_POSTGRESQL_<COLOR>_URL variables.
But you aren't using these variables. The DB_HOST, DB_PORT, etc., environment variables aren't set by Heroku, and Heroku won't update them for you. Rather than manually setting those variables, I strongly urge you to use the variables Heroku provides:
The value of your app’s DATABASE_URL config var can change at any time. Do not rely on this value either inside or outside your Heroku app.
I'm not familiar with TypeORM, but it looks like you can pass it a url directly:
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
const isProduction = configService.get('STAGE') === 'prod';
return {
ssl: isProduction,
extra: {
ssl: isProduction ? { rejectUnauthorized: false } : null,
},
type: 'postgres',
autoLoadEntities: true,
synchronize: true,
url: configService.get('DATABASE_URL'), // <-- here
};
},
}),
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
I am working on a NestJs project using Bull queue.
Here is the code I am using to connect Redis, and it works well。
BullModule.registerQueueAsync(
{
name: 'test',
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
redis: {
host: configService.get('REDIS_ADDR'),
port: configService.get('REDIS_PORT'),
},
}),
inject: [ConfigService],
}
)
Now, I need to switch to using Redis Sentinel.
I searched online, but I could not find an appropriate tutorial.
I appreciate any help you can provide.
I built a Http app and 2 microservices using TCP protocol.
This is my application diagram.
// Http App/app.service.ts
constructor() {
this.accountService = ClientProxyFactory.create({
transport: Transport.TCP,
options: {
host: 'localhost',
port: 8877,
},
});
this.friendService = ClientProxyFactory.create({
transport: Transport.TCP,
options: {
host: 'localhost',
port: 8080,
},
});
}
I tried to send message from Account Service to Friend Service by #Messagepattern().
ClientProxy is set up each service. But it doesn't work.
I read offical documentaion #nestjs/microservices, But i don't know which one is appropriate.
Is there right way to send message from one microservice to another microservice?
You need to set up a message broker, something like RabbitMQ or Kafka, ie for RabbitMQ enter the command below and create a RabbitMQ container.
docker run -it --rm --name rabbitmq -p 0.0.0.0:5672:5672 -p 0.0.0.0:15672:15672 -d rabbitmq:3-management
Then pass RabbitMQ options to your main.ts bootstrap function:
async function bootstrap() {
const rabbitmqPort = 5672
const rabbitmqHost = 127.0.0.1
const app = await NestFactory.create(AppModule);
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.RMQ,
options: {
urls: [
`amqp://${rabbitmqHost}:${rabbitmqPort}`,
],
queue: 'myqueue',
queueOptions: {
durable: false,
},
},
});
app
.startAllMicroservices(() => {
logger.log('Microservice is listening!');
})
.listen(3000, () => {
logger.log('Api Server is listening on 3000');
});
}
bootstrap();
For receiving messages:
#MessagePattern('my_pattern')
async myController(
#Payload() data: MYDTO,
): Promise<MY TYPE> {
return await this.accountService.myFunction(data);
}
Now when a client sends a message on myqueue with my_pattern pattern, the data that client sends will be data which comes from #playload() annotation.
For sending messages on any queue you need to add RabbitMQ configurations to your application module, ie account.module.ts, by the assumption that you want to send a message on FriendService
const rabbitmqPort = 5672
const rabbitmqHost = 127.0.0.1
#Module({
imports: [
ClientsModule.registerAsync([
{
name: 'Friend',
useFactory: {
transport: Transport.RMQ,
options: {
urls: [
`amqp://${rabbitmqHost}:${rabbitmqPort}`,
],
queue: 'friend_queue',
queueOptions: {
durable: false,
},
},
}
},
]),
],
controllers: [AccountController],
providers: [AccountService],
})
export class AccountModule {}
And then inject Friend client to your service constructor like this:
#Inject('Friend')
private friendClient: ClientProxy,
Send messages like this:
const myVar = await this.friendClient.send('Some_pattern', {SOME DATA}).toPromise();
Set up all the above configurations for your both microservices and it shall work.
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.