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
},
Related
What I want to do is avoid circular dependency and at the same time now make my module extremely large. This issue is the following:
We have the entities Database and DatabaseUser and each one is its own separate module(DatabaseModule, DatabaseUserModule). How should I go about the issue where I have to create a DatabaseUser when a Database is created by also avoiding the usage of forwardRef?
The need to use forwardRef comes from the fact that the DatabaseService will require the DatabaseUserService in order to create the DatabaseUser for the given Database. And on the other side, the DatabaseUserService will need the DatabaseService to retrieve the Database for which the DatabaseUser is being created. Written in code it would look like so:
// src/database/database.dto.ts
export class CreateDatabaseDTO {
name: string;
password: string;
}
// src/database/database.service.ts
#Injectable()
export default class DatabaseService {
constructor(
private readonly databaseRepository: DatabaseRepository,
#Inject(forwardRef(() => DatabaseUserService))
private readonly databaseUserService: DatabaseUserService,
) {}
public async createDatabase(
createDatabaseDTO: CreateDatabaseDTO,
): Promise<Database> {
const database = await this.databaseRepository.createDatabase(
createDatabaseDTO.name,
);
await this.databaseUserService.createDatabaseUser({
name: createDatabaseDTO.name,
password: createDatabaseDTO.password,
databases: [database.id],
});
return database;
}
public async getDatabases(ids: number[]): Promise<Database[]> {
return [];
}
}
// src/database/database.module.ts
#Module({
imports: [forwardRef(() => DatabaseUserModule)],
providers: [DatabaseService, DatabaseRepository],
exports: [DatabaseService],
})
export default class DatabaseModule {}
// src/database-user/database-user.dto.ts
export class CreateDatabaseUserDTO {
name: string;
password: string;
databases: number[];
}
// src/database-user/database-user.service.ts
#Injectable()
export default class DatabaseUserService {
constructor(
private readonly databaseUserRepository: DatabaseUserRepository,
#Inject(forwardRef(() => DatabaseService))
private readonly databaseService: DatabaseService,
) {}
public async createDatabaseUser(
createDatabaseUserDTO: CreateDatabaseUserDTO,
): Promise<DatabaseUser> {
const databases = await this.databaseService.getDatabases(
createDatabaseUserDTO.databases,
);
return this.databaseUserRepository.createUser(
createDatabaseUserDTO.name,
createDatabaseUserDTO.password,
databases,
);
}
}
// src/database-user/database-user.module.ts
#Module({
imports: [forwardRef(() => DatabaseModule)],
providers: [DatabaseUserService, DatabaseUserRepository],
exports: [DatabaseUserService],
})
export default class DatabaseUserModule {}
I try to block some root if it's not an admin, but when I run the code I have a TypeError but I don't know how to resolve it.
Thanks
roles.guards.ts
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { Role } from './role.enums';
import { ROLES_KEY } from './roles.decorator';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((Role) => user.roles?.includes(Role));
}
}
articles.controllers.ts
#UseGuards(JwtAuthGuard)
#Roles(Role.Admin)
async addArticle(
#Body('title') artTitle: string,
#Body('description') artDescription: string,
#Body('url') artUrl: string,
#Body('cover') artCover: string,
#Body('content') artContent: string,
#Body('category') artCategory: string,
){
const generatedId = await this.articlesService.insertArticle(
artTitle,
artDescription,
artUrl,
artCover,
artContent,
artCategory
);
return { id: generatedId };
}
when I run the code I have a TypeError but I don't know how to resolve it.
Thanks
I'd like to add more detail to Jay McDoniel's answer since it still took me a few hours to get around this issue.
Create JWT.module.ts (JwtModule is already used by #nestjs/jwt hence my use of caps) file with the following:
import { ConfigModule, ConfigService } from "#nestjs/config";
import { JwtModule } from "#nestjs/jwt";
#Module({
imports: [
{
...JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secretOrKeyProvider: () => (configService.get<string>("JWT_SECRET")),
signOptions: {
expiresIn: 3600,
},
}),
inject: [ConfigService],
}),
global: true
}
],
exports: [JwtModule]
})
export class JWTModule {}
Add this class to your app.module.ts's imports array.
if you have
{
provide: APP_GUARD,
useClass: RolesGuard,
},
in any of your modules... DELETE IT. declaring guards in any providers will automatically make it global and endpoints which you don't want to be guarded will end up getting guarded (I'm aware https://docs.nestjs.com/security/authorization#basic-rbac-implementation tells you to register the role guard in your providers but that just didn't work for me). You only need to import your strategies to the relevant routes.
In your controller, this should now work
#ApiBearerAuth()
#Roles(Role.Admin)
#UseGuards(JwtAuthGuard, RolesGuard)
#Get()
findAll() {
return this.usersService.findAll();
}
so this endpoint accepts users with a valid JWT and an admin role inside said JWT.
If I had to bet, your RolesGuard is bound to the global scope, whereas the JwtAuthGuard is bound to the route handler scope. Global guards execute first and foremost, so the RolesGuard executes before the JwtAuthGaurd can set req.user (passport is what does this under the hood). What you can do is either ensure that there is a req.user property (either via a middleware or jutt running the JwtAuthGuard globally) or you can move the RolesGuard to be scoped at the route handler level after the JwtAuthGuard runs.
Use JwtGuard and RoleGuard in the controller like #UseGuards(JwtAuthGuard, RolesGuard). The issue because of RoleGuards is not used in guard.
#Roles(Role.ADMIN)
#UseGuards(JwtAuthGuard,RolesGuard)
#Query(() => [User], { name: 'User' })
articles.module.ts
in this module file update in provider rolesGuards
providers: [AuthResolver, AuthService,LocalStrategy,JwtStrategy,RolesGuard],
use #Post() above your controller
#UseGuards(JwtAuthGuard)
#Roles(Role.Admin)
#Post('')
async addArticle(
#Body('title') artTitle: string,
#Body('description') artDescription: string,
#Body('url') artUrl: string,
#Body('cover') artCover: string,
#Body('content') artContent: string,
#Body('category') artCategory: string,
){
const generatedId = await this.articlesService.insertArticle(
artTitle,
artDescription,
artUrl,
artCover,
artContent,
artCategory
);
return { id: generatedId };
}
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.
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 want to import the config provider from the documentation into another provider.
I am using the same config structure that is sown in the documentation here.
So the config.module.ts looks like this:
import { Module, Global } from '#nestjs/common';
import { ConfigService } from './config.service';
#Global()
#Module({
providers: [
{
provide: ConfigService,
useValue: new ConfigService(
`config/env/${process.env.NODE_ENV || 'development'}.env`,
),
},
],
exports: [ConfigService],
})
export class ConfigModule {}
And the other provider should look something like this correct?
token.module.ts
import { Module, Global } from '#nestjs/common';
import { TokenService} from './token.service';
import { ConfigService } from './config.service';
#Global()
#Module({
import: [ConfigService]
providers: [TokenService],
exports: [TokenService],
})
export class TokenModule {}
While the TokenService should look something like this
token.service.ts
import { ConfigService } from '../../config/config.service';
export class TokenService {
constructor(private readonly configService: ConfigService) {}
test(): string {
return this.configService.get('TestVal');
}
}
But when I import TokenModule in AppModule I get this error The "path" argument must be one of type string, Buffer, or URL. Received type undefined
You forgot to mark your TokenService as #Injectable(). You need to tell NestJS about arguments that need to be provided to other services.