I have a subscriber for NestJS to listen to any create, update or delete events (TypeORM). When one of these events is fired, I'd like to use an injected service in order to create a new revision entry.
However, it seems I cannot get the dependency loaded inside of the subscriber and the service comes back as being undefined
Key files:
EntityModificationSubscriber (Subscriber)
RevisionEntity (Entity)
app.module.ts
#Module({
imports: [
HttpModule,
TypeOrmModule.forRoot({
type: (process.env.DB_TYPE as any) || 'postgres',
host: process.env.DB_HOST || '127.0.0.1',
port: (process.env.DB_PORT as any) || 5432,
username: process.env.DB_USER || 'root',
password: process.env.DB_PASS || '',
database: process.env.DB_NAME || 'test',
entities: [join(__dirname, '**/**.entity{.ts,.js}')],
synchronize: true,
logging: 'all',
logger: 'advanced-console',
subscribers: [EntityModificationSubscriber],
}),
TypeOrmModule.forFeature([
RevisionEntity,
]),
TerminusModule.forRootAsync({
// Inject the TypeOrmHealthIndicator provided by nestjs/terminus
inject: [TypeOrmHealthIndicator, MicroserviceHealthIndicator],
useFactory: (db, msg) => getTerminusOptions(db, msg),
}),
GraphQLModule.forRoot({
debug: true,
playground: true,
typePaths: ['./**/*.graphql'],
}),
],
controllers: [AppController],
providers: [
RevisionService,
EntityModificationSubscriber,
],
})
entity_modification_subscriber.ts
import {EntitySubscriberInterface, EventSubscriber, InsertEvent, RemoveEvent, UpdateEvent} from 'typeorm';
import {RevisionEntity, RevisonEntityStatus} from '../entities/revison.entity';
import {RevisionService} from '../services/revisions.service';
import {Injectable} from '#nestjs/common';
#Injectable()
#EventSubscriber()
export class EntityModificationSubscriber implements EntitySubscriberInterface {
constructor(private revisionService: RevisionService) {
}
// tslint:disable-next-line:no-empty
afterInsert(event: InsertEvent<any>): Promise<any> | void {
const revision = new RevisionEntity();
revision.action = RevisonEntityStatus.Created;
}
afterUpdate(event: UpdateEvent<any>): Promise<any> | void {
}
// tslint:disable-next-line:no-empty
afterRemove(event: RemoveEvent<any>) {
// this.revisionService.createRevisionEntry(revision);
}
}
The only way I found to inject a dependency into a subscriber using NestJS, was not to register that subscriber in the TypeORM configuration. I subscribe it manually into the TypeORM connection on subscriber's constructor.
import { EntitySubscriberInterface, EventSubscriber, InsertEvent, RemoveEvent, UpdateEvent, Connection } from 'typeorm';
import { RevisionEntity, RevisonEntityStatus } from '../entities/revison.entity';
import { RevisionService } from '../services/revisions.service';
import { Injectable } from '#nestjs/common';
#Injectable()
#EventSubscriber()
export class EntityModificationSubscriber implements EntitySubscriberInterface {
constructor(private readonly connection: Connection, private readonly revisionService: RevisionService) {
connection.subscribers.push(this); // <---- THIS
}
// tslint:disable-next-line:no-empty
afterInsert(event: InsertEvent<any>): Promise<any> | void {
const revision = new RevisionEntity();
revision.action = RevisonEntityStatus.Created;
//this.revisionService <- should be working!
}
afterUpdate(event: UpdateEvent<any>): Promise<any> | void {
}
// tslint:disable-next-line:no-empty
afterRemove(event: RemoveEvent<any>) {
// this.revisionService.createRevisionEntry(revision);
}
}
Then in your app.module on the TypeORM module configuration (TypeOrmModule.forRoot).
Remove the line:
subscribers: [EntityModificationSubscriber],
It solved to me, hope it help others.
You can find discussions about that in some NestJS issues/pull requests.
https://github.com/nestjs/typeorm/issues/85
https://github.com/nestjs/typeorm/pull/27
Hi the problem is that If you want to preform database actions you would have to use:
event.manager
and Don't use getEntityManager() or getRepository() or any other global function.
This is due to a transaction. Save is running in a transaction and your data is saved in a transaction which is not committed yet. But global functions you use are running out of transaction.
more about issue here:
https://github.com/typeorm/typeorm/issues/681
Related
I have a Nest JS application using TypeORM 0.3 version. The application is not starting with the new DataSource configuration.
Error: Nest JS application is not starting with Data source new typeORM 0.3 way.
ERROR MESSAGE:
Blocked in below error. Not sure how to fix it.
Nest can't resolve dependencies of the TypeOrmModuleOptions (?). Please make sure that the argument dependency at index [0] is available in the TypeOrmCoreModule context.
Potential solutions:
If dependency is a provider, is it part of the current TypeOrmCoreModule?
If dependency is exported from a separate #Module, is that module imported within TypeOrmCoreModule?
Code:
src/db/data-source.ts
This contains TypeORM 0.3 Data source connection file
```
import {
TypeOrmModuleAsyncOptions,
TypeOrmModuleOptions,
} from '#nestjs/typeorm';
// import { Tag } from '#app/tag/tag.entity';
import { DataSource, DataSourceOptions } from 'typeorm';
export const dataSourceOptions: DataSourceOptions = {
migrationsTableName: 'migrations',
type: 'postgres',
host: process.env.HOST,
port: +process.env.PORT,
username: process.env.USERNAME,
password: process.env.PASSWORD,
database: process.env.DATABASE,
migrations: [__dirname + '\\migrations\\**\\*{.ts,.js}'],
// entities: [Tag],
// entities: [__dirname + '/**/*.entity{.ts,.js}'],
// migrations: [__dirname + '/../database/migrations/*{.ts,.js}'],
// cli: {
// migrationsDir: __dirname + '/../database/migrations',
// },
// extra: {
// charset: 'utf8mb4_unicode_ci',
// },
logging: true,
synchronize: false,
};
const dataSource = new DataSource(dataSourceOptions);
export default dataSource;
```
In Nest JS App module file imported Data source and added to imports as suggested in official documentation
Any help will be appreciated team.
Screenshots: Code and Error
Code: commit
src/app.module.ts
```
import { forwardRef, Module } from '#nestjs/common';
import { dataSourceOptions } from '#app/db/data-source';
import { AppController } from '#app/app.controller';
import { AppService } from '#app/app.service';
import { TagModule } from '#app/tag/tag.module';
import { TypeOrmModule } from '#nestjs/typeorm';
import { ConfigModule, ConfigService } from '#nestjs/config';
// import { Tag } from './tag/tag.entity';
#Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRootAsync(dataSourceOptions),
// forwardRef(() => TagModule),
// TagModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
```
I used to have:
import { getManager } from "typeorm";
return getManager()
.count(entity, {
...where,
})
.then((count) => count < 1);
to use the current connection in a validation decorator and access to database.
But now with the version 0.3.0 of typeorm, getManager() is deprecated and I get the following error:
`ConnectionNotFoundError: Connection default was not found`
How should I use the new DataSource API to get the current connection using Nest.js in external scripts like validation decorators?
I have dealt with the following error with "typeorm": "0.3.6", before, so hopes that my experience will be relevant for you.
Instead of using getManager I am using entityManager but result is the same.
Here is the example for your case:
test.module.ts
#Module({
imports: [
TypeOrmModule.forRoot(postgresConfig), // <- connection configs
TypeOrmModule.forFeature([
TestEntity,
]),
],
controllers: [],
providers: [TestService],
})
export class TestModule {}
test.service.ts
#Injectable()
export class TestService implements OnApplicationBootstrap {
constructor(
#InjectEntityManager()
private readonly entityManager: EntityManager,
)
async onApplicationBootstrap(): Promise<void> {
const count = await this.entityManager.getRepository(TestEntity).count('query');
console.log(count);
}
}
You should also have created a typeorm entities for that.
If we are talking about migrations you could use an ormconfig.json, but also there was a problem with using typeorm combined with test frameworks like Jest and if you case is relevant with it, just mention it, and I'll find an example in on of my projects.
It should be something relevant with creating manual connections without decorator like:
export const getDatabaseConnectionOptions = (config): PostgresConnectionOptions => {
return {
type: 'postgres',
logging: config.db.logging,
connectTimeoutMS: config.db.master.connectionTimeoutMillis,
uuidExtension: 'uuid-ossp',
extra: {
idleTimeoutMillis: config.db.master.idleTimeoutMillis,
max: config.db.master.max,
keepalives_idle: config.db.master.keepalives_idle,
},
entities: process.env.TS_NODE
? [path.join(process.cwd(), 'src/**/*.entity.ts')]
: [path.join(process.cwd(), 'dist/**/*.entity.js')],
migrations: process.env.TS_NODE ? ['src/typeorm_migrations/*.ts'] : ['dist/typeorm_migrations/*.js'],
migrationsTableName: 'typeorm_migrations',
namingStrategy: new DatabaseNamingStrategy(),
replication: {
master: {
host: config.db.master.host,
port: config.db.master.port,
username: config.db.master.user,
password: config.db.master.password,
database: config.db.master.database,
},
slaves: [
{
host: config.db.slave.host,
port: config.db.slave.port,
username: config.db.slave.user,
password: config.db.slave.password,
database: config.db.slave.database,
},
],
},
cli: {
migrationsDir: 'src/typeorm_migrations',
},
};
};
export const createDatabaseConnection = async (config): Promise<Connection> => {
// Setup TypeDI
useContainer(Container);
// Setup typeorm-transactional-cls-hooked
initializeTransactionalContext();
patchTypeORMRepositoryWithBaseRepository();
return await createConnection(config);
};
const connection = await createDatabaseConnection(getDatabaseConnectionOptions(config));
const { app } = createAppApi();
and then access to managers or entities via connection props.
I am using TypeORM by way of NestJS to connect to a PostgreSQL database. For every connection I make to the database, I need to 'SET ROLE my-service', preferably just once when the connection is established. What are my options?
I have tried to find a way using the config that's passed to TypeOrmModule.forRoot(). I've considered the possibility that I can issue instructions to the PostgreSQL client ahead of time, but I don't have a strong sense of how to do that.
Please scroll to Update 2, per your unpated question.
Option 1: As an injectable service
You can create a service SomeTypeOrmConfigService as an #Injectable(), and then inject it like so.
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '#nestjs/typeorm';
// create your injectable here
#Injectable()
export class SomeTypeOrmConfigService implements TypeOrmOptionsFactory {
constructor(private configService: ConfigService) {}
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: 'mysql',
host: this.configService.get('DATABASE_HOST'),
username: this.configService.get('DATABASE_USERNAME'),
password: this.configService.get('DATABASE_PASSWORD'),
};
}
}
Option 2: Save the information in a common .env, or b) another approach database.provider.ts file and export that copied ans./credit to from here
import { ConfigModule, ConfigService } from '#nestjs/config';
import { TypeOrmModule } from '#nestjs/typeorm';
export const databaseProviders = [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('PGHOST'),
port: +configService.get<number>('PGPORT'),
username: configService.get('PGUSER'),
password: configService.get('PGPASSWORD'),
database: configService.get('PGDATABASE'),
entities: ['dist/**/*.entity{.ts,.js}'],
synchronize: false,
logging: true,
}),
}),
];
Then import it in your database.module.ts
import { databaseProviders } from './database.provider';
import { DatabaseService } from './database.service';
import { Module } from '#nestjs/common';
#Module({
imports: [...databaseProviders],
providers: [DatabaseService],
exports: [...databaseProviders],
})
export class DatabaseModule {}
And that's all, you can add multiple connections in your database.provider.ts if you want it, also, donĀ“t forget to create the .env and import the database module in your root module.
Update Answer (V2) to updated question
In a nutshell.. I think you are missing a) session module like nestjs-session , once you use that you can optionally b) cache it in your role with your session object
Step 1: use nestjs-session
Step 2: Now you can have access/use role in the session property object of your request
Step 3: You can then make it an injectable or a user factory with the help of the NestSessionOptions
My humble recommendation, save yourself time, use a pre-rolled package as a decorator.. try this package from here - use their sample ref. below ->
npm i nestjs-roles
1. Create Your roles
// role.enum.ts
export enum Role {
ADMIN = 'ADMIN',
USER = 'USER',
MODERATOR = 'MODERATOR',
}
2. Let's say you use nestjs-session and keep role in session property object of request. So then create guard with getRole callback:
// roles.guard.ts
import { ExecutionContext } from '#nestjs/common';
import { createRolesGuard } from 'nestjs-roles';
import { Role } from './role.enum';
function getRole(context: ExecutionContext) {
const { session } = context.switchToHttp().getRequest();
if (!session) {
return;
}
return (session as { role?: Role }).role;
}
export const Roles = createRolesGuard<Role>(getRole);
3. After that we can set Roles guard globally (don't forget to pass Reflector instance):
// bootstrap.ts
import { NestFactory, Reflector } from '#nestjs/core';
import { Roles } from './roles.guard';
const app = await NestFactory.create(AppModule);
const reflector = app.get<Reflector>(Reflector);
app.useGlobalGuards(new Roles(reflector));
4. All settings are done. Now you can set up access in your controllers:
// secrets.controller.ts
import { Roles } from './roles.guard';
#Controller('secrets')
#Roles.Params(true) // setup access on Controller for users with any existing role
export class SecretsController {
#Get('my')
async readMy() {
// ...
}
#Patch(':id')
#Roles.Params(Role.ADMIN) // override access on certain handler
async update() {
// ...
}
}
Connecting to two databases using TypeORM and NestJS throws a ConnectionNotFoundError when a custom repository is registered using the connection (UserConnection & PhotoConnection) one for each database. I have created a minimal repo here https://github.com/masonridge/connissuewithndb. The TypeOrm registration is done in the AppModule of NestJS
(app.module.ts)
#Module({
imports: [
TypeOrmModule.forRootAsync({
name: 'PhotoConnection',
useFactory: async () => {
return {
type: 'sqlite',
synchronize: true,
database: 'TestPhoto.sqlite',
entities: [Photo],
} as SqliteConnectionOptions;
},
}),
TypeOrmModule.forRootAsync({
name: 'UserConnection',
useFactory: async () => {
return {
type: 'sqlite',
synchronize: true,
database: 'TestUser.sqlite',
entities: [User],
} as SqliteConnectionOptions;
},
}),
// TypeOrmModule.forFeature([User, UserRepository], 'UserConnection'),
// TypeOrmModule.forFeature([Photo, PhotoRepository], 'PhotoConnection'),
PhotoModule, UserModule,
],
(photo.module.ts) DB connection1 - PhotoConnection is registered here
#Module({
imports: [
TypeOrmModule.forFeature([PhotoRepository], 'PhotoConnection'),
],
providers: [
PhotoService,
],
controllers: [PhotoController],
exports: [TypeOrmModule],
})
export class PhotoModule {}
(user.module.ts) DB connection2 - UserConnection is registered here
#Module({
imports: [
TypeOrmModule.forFeature([UserRepository], 'UserConnection'),
],
providers: [
UserService,
],
exports: [TypeOrmModule],
controllers: [UserController],
})
export class UserModule {}
(user.repository.ts) Custom repository
#EntityRepository(User)
export class UserRepository extends Repository<User> {
async createUser(name: string): Promise<string> {
const user = this.create();
user.username = name;
user.salt = 'salt';
user.password = 'xxkdkdk';
user.save();
return name;
}
}
(photo.repository.ts)
#EntityRepository(Photo)
export class PhotoRepository extends Repository<Photo> {
async createPhoto(name: string): Promise<string> {
const photo = this.create();
photo.name = name;
photo.save();
return name;
}
}
The repo is injected into the service using the connection (PhotoConnection)
(photo.service.ts)
export class PhotoService {
constructor(
#InjectRepository(Photo, 'PhotoConnection')
private readonly photoRepository: PhotoRepository,
) {}
and here using UserConnection (user.service.ts)
#Injectable()
export class UserService {
constructor(
#InjectRepository(User, 'UserConnection')
private readonly userRepository: UserRepository,
) {}
The application starts fine but on a POST request it throws a ConnectionNotFoundError error
(node:190812) UnhandledPromiseRejectionWarning: ConnectionNotFoundError: Connection "default" was not found.
at new ConnectionNotFoundError (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\error\ConnectionNotFoundError.js:10:28)
at ConnectionManager.get (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\connection\ConnectionManager.js:38:19)
at Object.getConnection (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\index.js:244:35)
at Function.BaseEntity.getRepository (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\repository\BaseEntity.js:67:57)
at Photo.BaseEntity.save (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\repository\BaseEntity.js:27:33)
at PhotoRepository.createPhoto (C:\nestjs\type-orm-dbscratch\dist\db\photo\photo.repository.js:15:15)
at PhotoService.createPhoto (C:\nestjs\type-orm-dbscratch\dist\db\photo\photo.service.js:24:43)
at PhotoController.addSetting (C:\nestjs\type-orm-dbscratch\dist\db\photo\photo.controller.js:22:27)
at C:\nestjs\type-orm-dbscratch\node_modules#nestjs\core\router\router-execution-context.js:37:29
at process._tickCallback (internal/process/next_tick.js:68:7)
I would like to know if there is an issue with the registration. Any help would be appreciated.
It looks like the issue is between using the TypeORM approach of DataMapper and ActiveRecord. It seems using the ActiveRecord approach does not support named repositories with NestJS, and as such was looking for connection named 'default'. If you remove the extends BaseEntity and use the repository's save method instead this.save(user) vs user.save() you'll get a successful save. I didn't see an immediate way to set the connection of the ActiveRecord but it may be an option somewhere if that is still the approach you would like to follow.
I have a NestJS project I have set up and am trying to use Sequelizer to access my database. Currently, I have a database provider and module set up to be used globally (the module is injected in my app.module) that are set up as follows:
database.module.ts:
import {Global, Module } from '#nestjs/common';
import { databaseProvider } from './database.provider'
#Global()
#Module({
providers: [databaseProvider],
exports: [databaseProvider]
})
export class DatabaseModule{}
database.provider.ts:
import {Sequelize } from 'sequelize-typescript';
export const databaseProvider = {
provide: 'SequelizeInstance',
useFactory: async () => {
const config = {
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
logging: false,
dialect: 'mssql',
force: false,
timezone: '-07:00'
};
const sequelize = new Sequelize(config);
return sequelize;
}
};
My module to call the code uses a module and service that is setup as follows:
db.module.ts:
import { Module } from '#nestjs/common';
import { DBService } from './db.service';
import { DBController } from './db.controller'
#Module({
controllers: [DBController],
providers: [DBService],
exports: [DBService]
})
export class DBModule {}
db.service.ts:
import { Inject, Injectable } from '#nestjs/common';
#Injectable()
export class DBService {
constructor(
#Inject('SequelizeInstance') private readonly sequelizeInstance
) {}
public async getRegions(): Promise<any> {
return this.sequelizeInstance.query('EXEC pr_region_lst')
.then(data => data)
.catch(error => console.log(error));
}
}
The problem I am having is that sequelizer keeps throwing an error that the Login failed for user ''. I attached a debugger and am looking at the code in the db.service.ts. When I look at sequelizeInstance, it shows the config having the username from the configuration file I provided. I don't know why it is not using it in the DB context though.
I've resolved my issue. It seems that the 'tedious' package was not installed on my project. The msssql package however was. So it was apparently not using the driver correctly to connect to the database. Once I added tedious, the connection started working.