NestJs monorepo shared lib injection - node.js

I have NestJS application with couple microservices stored in single repository (monorepo approach).
AccessControl module stores in libs, it should be shared across multiple microservices. It has AccessControlModule.ts file
#Global()
#Module({
providers: [
{
provide: 'CONNECTION1',
useFactory: (configService: ConfigService) => {
return ClientProxyFactory.create(
configService.getRMQConnection(),
);
},
inject: [ConfigService],
},
ACGuard,
],
exports: [ACGuard],
imports: [ConfigModule],
})
export class AccessControlModule implements OnModuleDestroy {
constructor(
#Inject('CONNECTION1')
protected readonly orgConnection: ClientProxy,
) {}
onModuleDestroy(): any {
this.orgConnection.close();
}
}
This file responsible for module description, it creates connection for another microservice and provide it to ACGuard service. ACGuard.ts:
#Injectable()
export class ACGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
private config: ConfigService,
#Inject('CONNECTION1')
private readonly userConnection: ClientProxy;
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
// do some stuff
}
}
This part by itself compiles well and logically works fine. Problem begins when I try to inject it into one of microservices. I do it as usual by adding AccessControlModule into import part of some module. For example KioskModule:
#Module({
imports: [
...
AccessControlModule
],
providers: [
...
KiosksResolver
]
})
export class KiosksModule {}
Since AccessControlModule marked as Global and exports ACGuard I expect it to be injectable into my providers.
#Resolver('Kiosk')
export class KiosksResolver {
...
#UseGuards(ACGuard)
#Query()
kiosks() {
// ...
}
...
}
But this code falls on the compilation step with error:
[Nest] 9964 - 05/07/2020, 9:33:02 PM [ExceptionHandler] Nest can't resolve dependencies of the ACGuard (Reflector, ConfigService, ?). Please make sure that the argument CONNECTION1 at index [2] is available in the KiosksModule context.
On the other hand, if i inject it in KiosksResolver's constructor, application builds successfully.
I will appreciate any help and ideas, thanks!

The way how i solved this issue was exporting CONNECTION1 provider in AccessControlModule.ts.
#Module({
providers: [
{
provide: 'CONNECTION1',
useFactory: (configService: ConfigService) => {
return ClientProxyFactory.create(
configService.getRMQConnection(),
);
},
inject: [ConfigService],
},
ACGuard,
],
exports: [ACGuard, 'CONNECTION1'],
imports: [ConfigModule],
})
export class AccessControlModule ...
With this export KioskModule creates it's own ACGuard but provides here connection exported from AccessControlModule.
It's not clear for me why KioskModule doesn't get built instance of ACGuard exported from AccessControlModule but try build it once more.

Related

NestJS Repository seems to be injected but is in-fact empty?

I'm currently working on an API with NestJS and Sequelize, and it seems that there is a problem when injecting the repository.
For context, when the service calls this.repo.findByKey the function is undefined and the program fails, but when inspecting with VSCode's debugger, the proptotype had the function inside of it.
Here is a reproduction of the source :
// service.ts
#Injectable()
export class Service {
constructor(
#Inject('REPOSITORY') private repo: IRepository
) {
super();
}
async getByCode(code: string) {
console.log(this.repo); >> [class Repository extends BaseRepository]
console.log(this.repo.findByKey); >> undefined
return res = await this.repo.findByKey(code); >> this.repo.findByKey is not a function
}
}
// repository.ts
#Injectable()
export class Repository
extends BaseRepository
implements IRepository
{
constructor(
#Inject('DATABASE_HELPER')
databseHelper: DatabaseHelper,
private mapper: CountryMapper,
) {
super(databseHelper);
}
async findByKey(code: string) -> Promise<DTO> {
... never reached
}
}
I cannot find the origim of this problem, is there a dark magician here to help me?
Per request, here is the module configuration :
#Module({})
export class ExampleModule {
static register(typeClass: Type<IService>): DynamicModule {
return {
imports: [
HelperModule,
SequelizeModule.forFeature([Entity]),
],
module: ExampleModule,
providers: [
ExampleMapper,
{
provide: 'REPOSITORY',
useValue: Repository,
},
{
provide: 'SERVICE',
useClass: typeClass,
},
],
controllers: [Controller],
};
}
}
You are injecting the class itself. So this.repo is a constructor that never has been called.
{
provide: 'REPOSITORY',
useValue: Repository,
},
useValue is suitable for shared values like config objects, connection strings, etc as the name imply.
If you want to inject class instance you need to configure your provider like this
{
provide: 'REPOSITORY',
useClass: Repository, // <-- notice useClass
},

Nest JS's Throttler Guard from REST module is breaking GraphQL module

I have a Nest.js app with a REST module and a GraphQL module. Both are imported into an App.module.ts. I'm using Nest's Throttler Guard to protect the whole application. As it's already known, GraphQL does not work with the normal ThrottlerGuard, so I created a GqlThrottlerGuard and imported it on the GraphQL module, while importing the original ThrottlerGuard on the REST module.
So, my graphQL module looks like this:
#Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true
}),
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
ttl: config.get('security.throttle.ttl'),
limit: config.get('security.throttle.limit'),
}),
}),
],
providers: [
{
provide: APP_GUARD,
useClass: GqlThrottlerGuard,
},
],
})
export class GraphModule { }
And the REST module, like this:
#Module({
imports: [
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
ttl: config.get('security.throttle.ttl'),
limit: config.get('security.throttle.limit'),
}),
}),
],
controllers: [RestController],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class RestModule implements NestModule {}
Finally, both modules are imported to the App Module, which is the module I actually run:
#Module({
imports: [
RestModule,
GraphModule,
],
})
export class AppModule { }
For some reason, the error seen here is still happening to me on the GraphModule, even though the normal ThrottlerGuard is imported only on the RestModule. Should it work like this? How can I solve it?
APP_GUARD is a global binding, it applies to all routes. You should make one coherent guard that returns the proper request response based on the ExecutionContext#getType method which will either return http or graphql
#Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {
getRequestResponse(context: ExecutionContext) {
const reqType = context.getType<ContextType | 'graphql'>()
if (reqType === 'graphql') {
const gqlCtx = GqlExecutionContext.create(context);
const ctx = gqlCtx.getContext();
return { req: ctx.req, res: ctx.res };
} else if (reqType === 'http') {
return {
req: context.switchToHttp().getRequest(),
res: context.switchToHttp().getResponse()
}
} else {
// handle rpc and ws if you have them, otherwise ignore and make previous `else if` just an `else`
}
}

NestJS -- JWT Dependency is not being loaded

I do not need complete passport, I just need to sign a JWT token.
I tried working with this repository
I tried all possible combinations, but I just cant integrate it in the project. I had followed the course of several different errors. I fix one, and the another pops up. So, I am including minimum part, and the current error that is thrown.
AuthModule:
import { Module } from '#nestjs/common';
import { JwtModule } from '#nestjs/jwt';
import { AuthService } from './auth.service';
import { JwtService } from '#nestjs/jwt';
#Module({
imports: [ JwtModule.register({ secret: 'hard!to-guess_secret' })],
providers: [AuthService],
exports: [AuthService, JwtService]
})
export class AuthModule {}
AuthService:
import { Injectable } from '#nestjs/common';
import { UsersService } from '../users/users.service';
import { JwtService } from '#nestjs/jwt';
#Injectable()
export class AuthService {
constructor(
private readonly jwtService: JwtService,
) {}
async signPayload (user: any) {
const payload = { username: 'HARDCORE', color:'RED' };
return {
whatever: this.jwtService.sign(payload),
};
}
}
AppModule:
#Module({
imports: [
ConfigModule.forRoot(),
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: 'wefwefwef',
}),
inject: [ConfigService],
}),
TypeOrmModule.forRoot({
type: 'mysql',
...
}),
UsersModule,
SubscriptionsModule,
ProductsModule
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
}
SomeController:
export class UsersController {
constructor(private readonly usersService: UsersService,
private readonly authService: AuthService
) {}
...
#ApiResponse({ status: 401, description: 'Unauthorized.' })
#ApiResponse({ status: 404, description: 'User Not Found.' })
#Get('users')
async findOne(#Query() userGetDto: UserGetDto): Promise<User> {
const user = await this.usersService.findByUsername(userGetDto.userName);
if (!user) throw new NotFoundException('User Not Found')
let signedUser = this.authService.signPayload(user);
return user;
And this is the error with this setup that I get:
Nest can't resolve dependencies of the JwtService (?). Please make
sure that the argument JWT_MODULE_OPTIONS at index [0] is available in
the JwtService context.
I spend lot of time on this one, but I just cant make it work.
Based on your error, JwtService is in an imports array somewhere. Providers (classes marked with #Injectable()) never go in the imports array, but rather should be added to the exports array and that module where the exports was added should be put in the consuming module's imports array.
Also, if you are working with a Dynamic Module (any module that uses register or forRoot or an async variant of the two) you should always export the module instead of its services, as the module most likely has important configurations necessary for the service to work.

NestJS/TypeORM - connecting to multiple databases - ConnectionNotFoundError

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.

Mock multiple TypeORM repositories in NestJS

I'm having trouble to mock multiple repositories from different modules in NestJS.
I'm using a UsersRepository from a UsersModule inside another module service (NotesService). The code is working fine, but I can't make the unit tests work.
I have the following error: Error: Nest can't resolve dependencies of the UserRepository (?). Please make sure that the argument Connection at index [0] is available in the TypeOrmModule context.
Minimal reproduction
// [users.module.ts]
#Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
// [users.service.ts]
#Injectable()
export class UsersService {
constructor(#InjectRepository(User) private usersRepository: UsersRepository) {}
...
}
// [notes.module.ts]
#Module({
imports: [
TypeOrmModule.forFeature([Note, User]),
UsersModule,
],
controllers: [NotesController],
providers: [NotesService],
})
export class NotesModule {
}
// [notes.service.ts]
#Injectable()
export class NotesService {
constructor(
#InjectRepository(Note) private notesRepository: NotesRepository,
#InjectRepository(User) private usersRepository: UsersRepository,
) {}
...
}
Here is my unit test configuration:
// [notes.service.spec.ts]
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [UsersModule],
providers: [
NotesService,
{ provide: getRepositoryToken(Note), useFactory: repositoryMockFactory },
{ provide: getRepositoryToken(User), useFactory: repositoryMockFactory },
],
}).compile();
notesService = module.get<NotesService>(NotesService);
notesRepositoryMock = module.get(getRepositoryToken(Note));
});
The problem is I can't make a proper mock of the UsersRepository that comes from another module.
I tried importing TypeOrmModule directly inside the test, and everything I could but I can't make it work.
You don't need to import the UsersModule if you are directly providing the NotesService and the mocks that it will depend on. The reason for the error is that Nest is trying to resolve TypeormModule.forFeature([User]) from the UsersModule. Simply remove the import in the test and you should be golden.

Resources