How to use a custom middleware with nestjs-telegraf? - node.js

I am trying to implement custom Telegraf middleware with nestjs-telegraf library and connection to DB using Prisma.
My AppModule is:
#Module({
imports: [
TelegrafModule.forRootAsync({
imports: [ConfigModule, LoggerModule],
useFactory: (configService: ConfigService, logger: LoggerMiddleware) => {
return {
token: configService.get<string>("TELEGRAM_TOKEN")!,
middlewares: [sessionMiddleware, logger.use]
};
},
inject: [ConfigService, LoggerMiddleware]
}),
PrismaModule
],
controllers: [],
providers: [...someProviders]
})
export class AppModule {}
LoggerMiddleware:
#Injectable()
export class LoggerMiddleware {
constructor(private readonly prisma: PrismaService) {}
async use(ctx: Context, next: NextFunction) {
const listUser = await this.prisma.user.findMany()
console.log('listUser = ', listUser)
next()
}
}
LoggerModule:
#Module({
imports: [PrismaModule],
providers: [LoggerMiddleware, PrismaService],
exports: [LoggerMiddleware]
})
export class LoggerModule {}
It starts without errors and code reaches my logger middleware but then I am getting an error:
TypeError: Cannot read properties of undefined (reading 'prisma')
I have access to Prisma service from another provider and a connection to DB works.
At the start, nest initializes all dependencies successfully but I don't understand why during execution it fell with this error. What did I do wrong?

The answer was given by Alexander Bukhalo on github

Related

NestJS : TypeError: Cannot read property 'get' of undefined

I am trying to pass the connection parameters to mongodb by enviroment file and I am getting the following error
[Nest] 1176 - 12/10/2021 23:34:35 ERROR [ExceptionHandler] Cannot read property 'get' of undefined
TypeError: Cannot read property 'get' of undefined
at MongoService.createMongooseOptions (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/src/common/mongo/mongo.service.ts:20:41)
at Function.<anonymous> (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/mongoose/dist/mongoose-core.module.js:135:120)
at Generator.next (<anonymous>)
at /Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/mongoose/dist/mongoose-core.module.js:20:71
at new Promise (<anonymous>)
at __awaiter (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/mongoose/dist/mongoose-core.module.js:16:12)
at InstanceWrapper.useFactory [as metatype] (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/mongoose/dist/mongoose-core.module.js:135:45)
at Injector.instantiateClass (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/core/injector/injector.js:294:55)
at callback (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/core/injector/injector.js:43:41)
at Injector.resolveConstructorParams (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/core/injector/injector.js:119:24)
This is the service mongo.service.ts
import { MongooseModuleOptions, MongooseOptionsFactory } from '#nestjs/mongoose';
import { Configuration } from '../../config/config.keys';
import { ConfigService } from '../../config/config.service';
export class MongoService implements MongooseOptionsFactory {
constructor( private configService: ConfigService ) {}
createMongooseOptions(): MongooseModuleOptions {
const user = this.configService.get(Configuration.DB_MONGO_USER);
const password = this.configService.get(Configuration.DB_MONGO_PASSWORD);
const server = this.configService.get(Configuration.DB_MONGO_HOST);
const database = this.configService.get(Configuration.DB_MONGO_DATABASE);
return {
uri: `mongodb://${user}:${password}#${server}/${database}?retryWrites=true&w=majority`,
};
}
}
And this I import it in the app.module.ts
#Module({
imports: [
MongooseModule.forRootAsync({
useClass: MongoService,
}),
Any suggestion,
thanks,
JM
You need 2 things here:
Your MongoService needs to be marked with #Injectable() so that Nest can read the metadata of the constructor and set up the injection properly
If your ConfigModule does not have a globally exported ConfigService, then in MongooseModule.forRootAsync() along with the useClass you need to have imports: [ConfigModule] so that you can inject the ConfigService
#Module({
imports: [
MongooseModule.forRootAsync({
imports: [ ConfigModule ],
inject: [ConfigService],
useClass: MongoService
})
})

Nestjs middleware with dependencies

I am having trouble creating a middleware that has two dependencies (TypeORModule.forFeature([USER]), FirebaseModule).
What I have done is create an AuthModule which looks like this:
#Module({
imports: [
FirebaseModule,
TypeOrmModule.forFeature([User])
],
providers: [
AuthMiddleware
],
})
and the middleware which looks like this
export class AuthMiddleware implements NestMiddleware {
constructor(
#InjectRepository(User)
private usersRepository: Repository<User>,
private firebaseService: FirebaseService
) {}
async use(req: Request, res: Response, next: () => void) {...}
}
and my app module which looks like this
#Module({
imports: [
TypeOrmModule.forRoot({
...config.get("database"),
entities: [__dirname + '/entities/**/*.{js,ts}']
}),
AuthModule,
exampleModule
],
providers: [
AuthMiddleware
]
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): any {
consumer.apply(AuthMiddleware).forRoutes("*")
}
}
I get many errors and I try to shift things around to make it work but I simply can't get it to happen. I get errors from
Please make sure that the argument UserRepository at index [0] is available in the module(sometimes AppModule, sometimes exampleModule) context.
Do other modules (controller ones, as in providing api services) need to also import the middleware module if it applies to them too?
In general, how do I go on about implementing middlewares that depend on external modules? Do they have to be modules so I can import the requires modules?
I'd love some help, thanks!
You shiouldn't need to re-add AuthMiddleware to the AppModule's providers. It already exists in AuthModule. Also, you can bind the middleware inside the AuthModule if you want instead of in the AppModule and it will have the same global scope.

NestJS guard with GraphQL

I am trying to add an auth guard to a GraphQL resolver but it doesn't seem to be working as expected.
I have a simple guard that should reject all requests:
#Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
return false;
}
}
and have applied it in a resolver:
#Resolver()
export class RecipeResolver {
#Query(() => [Recipe])
#UseGuards(AuthGuard)
public recipes(): Recipe[] {
return this.recipeData;
}
}
This does not work in the resolver and the canActivate is never fired. This does work in an HTTP Controller.
Changing #UseGuards(AuthGuard) to #UseGuards(new AuthGuard()) works but will not go through the dependency injection process which is not ideal.
What am I doing wrong?
Edit:
original app.module.ts:
#Module({
imports: [
GraphQLModule.forRoot({
autoSchemaFile: 'shcema.gql',
playground: true,
}),
RecipeResolver,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
From the code, the resolver was in the wrong location (in an imports array instead of providers) which meant Nest didn't set up the request lifecycle properly.

Dotenv variable is undefined

I have one module (auth module) where I register JwtModule:
JwtModule.register({
secret: process.env.SECRET_KEY,
signOptions: { expiresIn: '60m' },
}),
Register function looks like this: static register(options: JwtModuleOptions): DynamicModule;
JwtModule is defined in node_modules (jwt.module.js)
let JwtModule = JwtModule_1 = class JwtModule {
static register(options) {
console.log(options);
console.log(process.env.SECRET_KEY);
return {
module: JwtModule_1,
providers: jwt_providers_1.createJwtProvider(options)
};
}
In app.module.ts I have
ConfigModule.forRoot({
isGlobal: true,
}),
process.env.SECRET_KEY is undefined in node_modules file (console.log(process.env.SECRET_KEY)), but signOptions: { expiresIn: '60m' } is defined.
If I try to write to console SECRET_KEY from .env everywhere else is defined, but in node_modules is undefined.
The ConfigModule's ConfigService runs dotenv's config() method, but all decorators run at the same time, so the config() method is not called yet. You can use the async registration instead and use the ConfigService like so
JwtModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get('SECRET_KEY')
})
})
Be sure that you Call the ConfigService in your App Module constructor :
export class AppModule {
constructor(private configService: ConfigService) {}
}

Can't use custom provider

I'm trying to inject a custom provider as per the documentation:
https://docs.nestjs.com/fundamentals/custom-providers
My service:
#Injectable({scope: Scope.REQUEST})
export class ReportService implements OnModuleInit {
private readonly reportRepository: Repository<Report>;
constructor(
public config: ConfigService,
private readonly moduleRef: ModuleRef,
#Inject('CONNECTION') connection: Connection,
) {
console.log(connection);
}
...
app.module.ts:
const connectionProvider = {
provide: 'CONNECTION',
useValue: connection,
};
#Module({
imports: [
ReportModule,
...
],
providers: [connectionProvider],
controllers: [AppController],
})
export class AppModule implements NestModule {
Doing so results in:
Error: Nest can't resolve dependencies of the ReportService (ConfigService, ModuleRef, ?). Please make sure that the argument at index [2] is available in the ReportModule context.
What am I missing?
If a provider should be available outside of the module you defined it in, you need to add it to exports in the module definition (app.module). The other module (report.module) using it needs to add the module to its imports definition.
app.module.ts
const connectionProvider = {
provide: 'CONNECTION',
useValue: Connection,
};
#Module({
imports: [
ReportModule,
...
],
providers: [connectionProvider],
exports: ['CONNECTION'],
controllers: [AppController],
})
export class AppModule implements NestModule {}
report.module.ts
#Module({
imports: [
AppModule
],
providers: [],
controllers: [],
})
export class ReportModule implements NestModule {}
In your case, this produces a circular dependency which needs to be resolved.
Since app.module seems to be your core module you could make it global but you still need to export the provider.
Alternatively, I found it to be a good practice to not define any providers in app.module and use DynamicModule (e.g. forRoot and forFeature static initiator functions) to only instantiate what is needed, but that seem to be outside of this question.

Resources