Why is my Nestjs Service being instantiated twice? - dependency-injection

I have the following NgrokService here:
#Injectable()
export class NgrokService
implements OnApplicationBootstrap, OnApplicationShutdown
{
port: string
ngrokToken: string
ngrok: typeof import('ngrok') | null = null
constructor(private readonly configService: ConfigService) {
this.port = this.configService.get('API_PORT') ?? '3001'
this.ngrokToken = this.configService.getOrThrow('NGROK_TOKEN')
if (process.env.NODE_ENV === 'development') this.ngrok = require('ngrok')
}
async onApplicationBootstrap() {
if (process.env.NODE_ENV !== 'development') {
return
}
this.tunnelUrl = await this.ngrok!.connect({
addr: `https://localhost:${this.port}`,
subdomain: '<my_subdomain>',
region: 'us',
authtoken: this.ngrokToken,
})
}
async onApplicationShutdown() {
if (process.env.NODE_ENV !== 'development') {
return
}
this.ngrok!.disconnect()
}
}
I'm using it here:
#Module({
imports: [],
controllers: [HealthController],
providers: [HealthService, AwsService, NgrokService],
})
export class HealthModule {}
and also here:
#Module({
imports: [
...
],
controllers: [...],
providers: [
...
NgrokService
],
})
export class AppModule {}
For some reason though, the onApplicationBootstrap hook gets called twice. After digging, I was only able to solve it by wrapping the service in a module and creating a global lock variable that checks if the connection was already made.
I just want to understand why this is happening. Why is Nestjs instantiating the service twice? Even wrapped in a module, the service gets instantiated twice.

providers are singleton by default per module
so if you have NgrokService registered (ie, in the providers array) in multiple modules, you'll get multiple instances.

Related

Can we export custom Guards from nestjs dynamic module and use it inside host module?

email-verification.guard.ts
#Injectable()
export class EmailVerificationGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const skipEmailVerification = this.reflector.get<boolean>('skipEmailVerification', context.getHandler());
if (skipEmailVerification) {
return true;
}
const request: Request = context.getArgs()[0];
if (!request.authPayload) {
throw new ForbiddenException('User not found');
}
if (!request.authPayload[Auth0Namespace.AppMetadata]) {
throw new ForbiddenException('Please verify your email before continuing');
}
return true;
}
}
dynamic-auth.module.ts
import { DynamicModule, Module } from '#nestjs/common';
import { authService } from './auth.service';
import { EmailVerificationGuard } from './email-verification.guard';
#Module({})
export class DynamicAuthModule {
static register(): DynamicModule {
return {
module: DynamicAuthModule,
providers: [authService, EmailVerificationGuard],
exports: [authService, EmailVerificationGuard],
};
}
}
app module (host)
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DynamicAuthModule, EmailVerificationGuard } from 'dyamic-auth-module';
#Module({
imports: [DynamicAuthModule.register({ folder: './config' })],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Can I use EmailVerificationGuard in this host module? If not why?
Note:
nestjs packages version: 8.4.7
I tried this but I get this reflector dependency issue, How to resolve this?
Error: Nest can't resolve dependencies of the EmailVerificationGuard (?). Please make sure that the argument Reflector at index [0] is available in the AppModule context.
Potential solutions:
- If Reflector is a provider, is it part of the current AppModule?
- If Reflector is exported from a separate #Module, is that module imported within AppModule?
#Module({
imports: [ /* the Module containing Reflector */ ]
})
Is there any other way to handle this?

How to inject service inside a guard Nest js

I have created a guard for global use. Whenever I am trying to inject my user service into the guard getting this error while compiling - Nest can't resolve dependencies of the VerifyUserGuard (?). Please make sure that the argument dependency at index [0] is available in the UserModule context.
My user service is a part of user module and inside the user service I have used #InjectRepository(User) private readonly _usersRepository: Repository<User>
Guard -
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { UserService } from '#v1/user';
import { VerifyUserDto } from '#v1/user/dto';
#Injectable()
export class VerifyUserGuard implements CanActivate {
//getting error because of this
constructor(private _userService: UserService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>();
const requestBody = request.body as unknown as VerifyUserDto;
console.log(requestBody);
const user = await this._userService.findOneByEmail(requestBody.email);
return true;
}
}
User module -
import { MailModule } from '#mail';
import { Module } from '#nestjs/common';
import { UserService } from './user.service';
import { User } from './entities/user.entity';
import { TypeOrmModule } from '#nestjs/typeorm';
import { UserController } from './user.controller';
#Module({
imports: [MailModule, TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
I believe you need
either to inject your module or service (with providing if not module) in AppModule if you connect your guard
or use #Global() decorator for your module with service, e.g.
#Global()
#Module({
imports: [MailModule, TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
Try this It worked for me. Use #Inject Decorator.
constructor(#Inject(AppService) private _userService: UserService) {}

How to set a parametr in the contructor of injection service?

I have a service, in which I inject another service with parametr in constructor.
Main service
export class Test1Service {
constructor(
test2Service: Test2Service
) {}
getIndex() {
console.log(111);
}
}
Inject service
#Injectable()
export class Test2Service {
item;
constructor(name) {
if (name === 'blog') {
this.item = 'item1';
} else {
this.item = 'item2';
}
}
}
Fot this will change providers import in module:
#Module({
controllers: [AppController],
providers: [
Test1Service,
{
provide: 'BLOG',
useValue: new Test2Service('blog'),
},
{
provide: 'ANALYTICS',
useValue: new Test2Service('analytics'),
}
],
})
export class AppModule {}
Use it in service
#Injectable()
export class Test1Service {
constructor(
#Inject('BLOG') public testBlog: Test2Service,
#Inject('ANALYTICS') public testAnalytics: Test2Service
) {}
getIndex() {
this.testBlog.getIndex()
this.testAnalytics.getIndex()
}
}

Implementing strategy in nest.js

I am trying to use the strategy pattern for the service, however the Module I try to use as context for strategy seems to only stick to one of the two. Here is the example code:
animal.module.ts
#Module({})
export class AnimalModule {
static register(strategy): DynamicModule {
return {
module: AnimalModule,
providers: [{ provide: 'STRATEGY', useValue: strategy }, AnimalService],
imports: [],
exports: [AnimalService]
};
}
}
animal.service.ts
#Injectable()
export class AnimalService {
constructor (#Inject('STRATEGY') private strategy) {
this.strategy = strategy
}
public makeSound() {
return this.strategy.makeSound()
}
}
cat.module.ts
#Module({
imports: [
AnimalModule.register(catStrategy),
],
controllers: [CatController],
providers: [CatService],
})
export class CatModule {}
cat.service.ts
#Injectable()
export class CatService {
constructor(
private readonly animalService: AnimalService,
) {}
public makeSound() {
return this.animalService.makeSound()
}
}
dog.module.ts
#Module({
imports: [
AnimalModule.register(dogStrategy),
],
controllers: [DogController],
providers: [DogService],
})
export class DogModule {}
dog.service.ts
#Injectable()
export class DogService {
constructor(
private readonly animalService: AnimalService,
) {}
public makeSound() {
return this.animalService.makeSound()
}
}
cat.strategy.ts
class CatStrategy {
public makeSound() {
return 'meow';
}
}
export const catStrategy = new CatStrategy();
Repo that replicates the issue: https://github.com/kunukmak/nestjs-strategy-problem-example
To clarify, both catService.makeSound and dogService.makeSound return "meow" in this case. Is it possible to make the dog bark?
I think you are looking for something like the following. Check the repo for the full example here. You can see below, we are registering a DynamicModule from the AnimalModule class:
#Module({
imports: [AnimalModule.register()],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
The DynamicModule returned from the register() call is responsible for determining what implementation of the AnimalModule to provide. This means we can customize the AnimalModule based on configuration in the environment.
#Module({})
export class AnimalModule {
public static register(): DynamicModule {
const AnimalClassProvider = AnimalModule.getClassProvider();
return {
module: AnimalModule,
controllers: [AnimalController],
providers: [AnimalClassProvider],
exports: [AnimalClassProvider],
};
}
private static getClassProvider(): ClassProvider<AnimalService> {
const animalStrategy = process.env.ANIMAL_STRATEGY as AnimalStrategy;
const AnimalServiceClass = AnimalModule.getClassFromStrategy(animalStrategy);
return {
provide: AnimalService,
useClass: AnimalServiceClass,
};
}
private static getClassFromStrategy(strategy: AnimalStrategy): Type<AnimalService> {
switch (strategy) {
case AnimalStrategy.CAT: return CatService;
case AnimalStrategy.DOG: return DogService;
default: return AnimalService;
}
}
}
AnimalStrategy in this case is just an enum used to determine which implementation of the service we should provide.
With this approach, we allow Nest to construct the Provider along with all its dependencies properly. We are only responsible for telling Nest which implementation it will construct when it encounters an AnimalService dependency. This allows the rest of our application to be unaware of the implementation and only use our AnimalService abstraction.
From our AnimalController:
#Controller('animal')
export class AnimalController {
constructor(private readonly animalService: AnimalService) {}
#Post()
create(#Body() createAnimalDto: CreateAnimalDto) {
return this.animalService.create(createAnimalDto);
}
// ...
}
to another service in our application:
#Injectable()
export class PetOwnerService {
constructor(
private readonly animalService: AnimalService,
private readonly petOwnerService: PetOwnerService,
) {}
feedPet(petName: string) {
const petIsHungry = this.petOwnerService.isPetHungry(petName);
if (petIsHungry) this.animalService.feed(petName);
// ...
}
}

How to inject dependency into TypeORM ValueTransformer or EntitySchema

In my NestJS project I use TypeORM with external schema definition. I want to use a ValueTransformer to encrypt sensitive data in some database columns, e.g. the email of the user should be encrypted.
The ValueTransformer depends on an encryption service from another module. I can't figure out how to inject that service via DI.
export const UserSchema = new EntitySchema<User>(<EntitySchemaOptions<User>>{
name: 'user',
columns: {
email: {
type: 'varchar',
nullable: false,
transformer: new EncryptedValueTransformer()
} as EntitySchemaColumnOptions,
},
});
export class EncryptedValueTransformer implements ValueTransformer {
#Inject()
private cryptoService: CryptoService
public to(value: unknown): string | unknown {
// error: cryptoService undefined
return this.cryptoService.encrypt(value);
}
public from(value: unknown): unknown {
// error: cryptoService undefined
return this.cryptoService.decrypt(value);
}
}
#Injectable()
export class CryptoService {
public constructor(#Inject('CONFIG_OPTIONS') private options: CryptoModuleOptions) {
// options contain a secret key from process.env
}
public encrypt(data: string): string | undefined | null { ... }
public decrypt(data: string): string | undefined | null { ... }
}
#Module({})
export class CryptoModule {
public static register(options: CryptoModuleOptions): DynamicModule {
return {
module: CryptoModule,
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options,
},
CryptoService,
],
exports: [CryptoService],
};
}
}
#Module({
imports: [
DomainModule,
CryptoModule.register({ secretKey: process.env.ENCRYPTION_KEY }),
TypeOrmModule.forFeature([
UserSchema,
UserTypeOrmRepository,
]),
],
providers: [
UserRepositoryProvider,
UserRepositoryTypeOrmAdapter,
],
exports: [
UserRepositoryProvider,
UserRepositoryTypeOrmAdapter,
],
})
export class PersistenceModule {}
With the above code the CryptoService instance in EncryptedValueTransformer is undefined. I searched related issues. According to this post https://stackoverflow.com/a/57593989/11964644 NestJS needs some context for DI to work. If that context is not given, you can manually provide the dependency.
My workaround now is this:
export class EncryptedValueTransformer implements ValueTransformer {
private cryptoService: CryptoService
public constructor() {
// tightly coupled now an process.env.ENCRYPTION_KEY still undefined at instantiation time
this.cryptoService = new CryptoService({ secretKey: process.env.ENCRYPTION_KEY });
}
}
But with this workaround the process.env variable is not yet resolvable at the point where the class is being instantiated, so I need to modify CryptoService in a way that it reads the env variable at runtime itself. And at this point the CryptoService is not reusable anymore.
How can I inject the CryptoService into the EncryptedValueTransformer or into UserSchema with this external schema setup? Or any better way to solve this?

Resources