NestJS: inject custom service into MongooseModule - node.js

Trying to inject my own GoogleService to use it in Facility schema's pre-save hook. But as soon as i add imports: [GoogleModule], pre-save hook is not invoked without any errors. Same type of injection works with default ConfigModule fine.
google.module.ts:
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
import { GoogleService } from './google.service';
#Module({
imports: [ConfigModule],
providers: [GoogleService],
exports: [GoogleService]
})
export class GoogleModule { }
facilities.module.ts:
import { MongooseModule } from '#nestjs/mongoose';
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
import * as zip2tz from 'zipcode-to-timezone';
import { FacilitiesService } from './facilities.service';
import { UsersModule } from '../users/users.module';
import { ShiftTemplatesModule } from '../shift-templates/shift-templates.module';
import { FacilitiesController } from './facilities.controller';
import { Facility, FacilitySchema, FacilityDocument } from './schemas/facility.schema';
import { MailerModule } from '../thirdparty/mailer/mailer.module';
import { ServicedStatesModule } from './../serviced-states/serviced-states.module';
import { GoogleModule } from '../thirdparty/google/google.module';
import { GoogleService } from '../thirdparty/google/google.service';
#Module({
imports: [
ShiftTemplatesModule,
MongooseModule.forFeatureAsync([{
imports: [GoogleModule],
inject: [GoogleService],
name: Facility.name,
useFactory: async (googleService: GoogleService) => {
const schema = FacilitySchema;
schema.pre<FacilityDocument>('save', async function (next) {
console.log('****************************8') //this isn't invoked, however it is with no imports
console.log('PRESAVE')
if (!this.timezone || this.isModified('address')) {
this.timezone = zip2tz.lookup(this.address.zip) || 'America/New_York';
}
if (!this.address.lat || !this.address.long || this.isModified('address')) {
this.address = await googleService.attachCoordinatesToAddress(this.address);
}
next();
});
return schema;
}
}]),
UsersModule,
MailerModule,
ConfigModule,
ServicedStatesModule,
GoogleModule
],
controllers: [FacilitiesController],
providers: [FacilitiesService],
exports: [FacilitiesService]
})
export class FacilitiesModule { }

Related

Nest can't resolve dependencies

I ran into a problem with NestJS dependencies, I just started learning Nest and still don't quite understand how to build the structure correctly.
Nest can't resolve dependencies of the ChatGateway (?). Please make sure that the argument ChatAuth at index [0] is available in the ChatGateway context.
My error in terminal
chat.module.ts
`
import { Module } from '#nestjs/common';
import { ChatAuth } from './chat.middlewares';
import { ChatGateway } from './chat.gateway';
import { AuthHelper } from '../auth/auth.helper';
import { JwtStrategy } from '../auth/auth.strategy';
#Module({
imports: [ChatGateway, ChatAuth],
controllers: [],
providers: [AuthHelper, JwtStrategy],
})
export class ChatModule {}
`
chat.gateway.ts
`
import {
SubscribeMessage,
WebSocketGateway,
OnGatewayInit,
WebSocketServer,
OnGatewayConnection,
OnGatewayDisconnect,
MessageBody,
} from '#nestjs/websockets';
import { Logger } from '#nestjs/common';
import { Socket, Server } from 'socket.io';
import { ChatAuth } from './chat.middlewares';
#WebSocketGateway(7000)
export class ChatGateway
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
#WebSocketServer() server: Server;
private chatAuthHelper: ChatAuth;
private logger: Logger = new Logger('ChatGateway');
constructor(chatAuthHelper: ChatAuth) {
this.chatAuthHelper = chatAuthHelper;
}
#SubscribeMessage('msgToServer')
handleMessage(client: Socket, payload: string): void {
console.log(payload);
this.server.emit('msgToClient', payload);
}
#SubscribeMessage('events')
handleEvent(#MessageBody() data: string): void {
const parsed = JSON.parse(JSON.stringify(data));
parsed.msg = parsed.msg + ' 3';
this.server.emit('onMessage', {
msg: 'New message',
content: parsed.msg,
});
}
afterInit(server: Server) {
this.logger.log('Init');
}
handleDisconnect(client: Socket) {
this.logger.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket, ...args: any[]) {
if (client.handshake.headers.authorization) {
const guard = this.chatAuthHelper.use(
client.handshake.headers.authorization,
);
}
this.logger.log(`Client connected: ${client.id}`);
}
}
`
chat.middlewares.ts
`
import { Injectable, NestMiddleware } from '#nestjs/common';
import { AuthHelper } from '../auth/auth.helper';
#Injectable()
export class ChatAuth implements NestMiddleware {
private helper: AuthHelper;
constructor(helper: AuthHelper) {
this.helper = helper;
}
public async use(token): Promise<object> {
const currentToken = token.split(' ')[1];
const user = await this.helper.validate(currentToken);
console.log(JSON.stringify(user));
return user;
}
}
`
app.module.ts
`
import { Module } from '#nestjs/common';
import * as path from 'path';
import { ConfigModule } from '#nestjs/config';
import { TypeOrmModule } from '#nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { getEnvPath } from './common/helper/env.helper';
import { TypeOrmConfigService } from './shared/typeorm/typeorm.service';
import { ApiModule } from './api/api.module';
import { ChatModule } from './api/chat/chat.module';
const getPathConfig: string = path.join(__dirname, '..', 'env');
const envFilePath: string = getEnvPath(getPathConfig);
#Module({
imports: [
ConfigModule.forRoot({ envFilePath, isGlobal: true }),
TypeOrmModule.forRootAsync({ useClass: TypeOrmConfigService }),
ApiModule,
ChatModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
`
Swapped ChatAuth / ChatModule imports
come on, read the docs :D
ChatAuth is not a module, then there's no reason it to be listed in the imports array.
the page https://docs.nestjs.com/websockets/gateways shows that the gateway should be in the providers array. Again, ChatGateway is not a module, then why did you put that into imports array? the docs are pretty clear on what is the role of each option of #Module({}).

How to mock configService as registerAs in my unit tests in nestjs

Hi I currently want to mock my configService injected as RegisterAs which allows me to have strong typing when accessing my environment variables, my problem with this is that when I mock the configService in my unit test and try to manipulate the environment values ​​it doesn't I'm making it.
I show some of what I have.
My Nestjs Project:
src/
|- config/
| |- environment.ts
|- modules/
| |- auth/
| |- auth.service.ts
| |- auth.service.spec.ts
|- app.module.ts
|- main.ts
|+- ...
app.module.ts
import * as Joi from 'joi';
import { AuthModule } from './modules/auth/auth.module';
import { ConfigModule } from '#nestjs/config';
import { Module } from '#nestjs/common';
import { ScheduleModule } from '#nestjs/schedule';
import env from './config/environment';
#Module({
imports: [
ScheduleModule.forRoot(),
ConfigModule.forRoot({ // <= I load the variables and validate them with Joi
isGlobal: true,
load: [env],
validationSchema: Joi.object().keys({
JWT_SESSION: Joi.string().trim(),
JWT_SESSION_EXP: Joi.number(),
}),
}),
AuthModule,
],
})
export class AppModule {}
config/environment.ts
import { registerAs } from '#nestjs/config';
export default registerAs('env', () => ({
jwtSession: process.env.JWT_SESSION || undefined,
expSession: Number(process.env.JWT_SESSION_EXP) || undefined,
}));
auth.service.ts
import * as moment from 'moment';
import { ConfigType } from '#nestjs/config';
import { Inject, Injectable, Logger } from '#nestjs/common';
import { AuthResponse } from './interfaces/login.interface';
import { AuthenticationDto } from './dto/auth.dto';
import { HttpService } from '#nestjs/axios';
import { JwtFidelity } from './interfaces/jwt-fidelity.interface';
import env from './../../config/environment';
import jwt_decode from 'jwt-decode';
import { lastValueFrom } from 'rxjs';
#Injectable()
export class AuthService {
constructor(
#Inject(env.KEY) private configService: ConfigType<typeof env>, // <= I inject the environment variables like this
) {}
isTokenExpired(): boolean { // <= This checks if the token has expired
if (!this.configService.jwtSession) return true;
if (!this.configService.expSession) return true;
return false;
}
}
auth.service.spec.ts
Note: this is where i would like you to help me.
import { ConfigModule, ConfigService } from '#nestjs/config';
import { HttpModule, HttpService } from '#nestjs/axios';
import { Test, TestingModule } from '#nestjs/testing';
import { AuthService } from '../auth.service';
import environment from '../../../config/environment';
import { of } from 'rxjs';
const env = () => ({
auth: {
partner: {
jwtSeesion:
'jwt-token',
expSession: '1661878302',
},
},
});
describe('AuthService', () => {
let service: AuthService;
let httpService: HttpService;
let configService: ConfigService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [HttpModule, ConfigModule.forRoot({ load: [environment] })],
providers: [
HttpService,
AuthService,
ConfigService,
{ provide: 'CONFIGURATION(env)', useFactory: env },
],
})
.overrideProvider(HttpService)
.useValue({ request: jest.fn() })
.overrideProvider(ConfigService)
.useValue({ jwtSession: jest.fn() })
.compile();
service = module.get<AuthService>(AuthService);
httpService = module.get<HttpService>(HttpService);
configService = module.get<ConfigService>(ConfigService);
});
it('should be defined', () => {
expect(service).toBeDefined();
expect(httpService).toBeDefined();
expect(configService).toBeDefined();
});
describe('AuthController.isTokenExpired', () => {
it('variable jwtSession undefined', async () => {
configService.jwtSession = undefined; // <= I would like the possibility of doing something like that
jest.spyOn(configService, 'jwtSession').mockReturnValue('hola' as any); // <= Or something like that
const isExpired = await service.isTokenExpired();
expect(isExpired).toBeTruthy();
});
});
});
I would like that at the level of each test I can be able to manipulate the value of the environment variables that I have injected with #Inject(env.KEY) private configService: ConfigType<typeof env>, in my auth.service.ts

NestJs can't resolve dependencies of the HeicService

I am getting the following error when trying to spin up nestjs
[Nest] 47548 - 04/23/2022, 10:41:12 AM ERROR [ExceptionHandler] Nest can't resolve dependencies of the HeicService (?, +). Please make sure that the argument dependency at index [0] is available in the HeicModule context.
Potential solutions:
- If dependency is a provider, is it part of the current HeicModule?
- If dependency is exported from a separate #Module, is that module imported within HeicModule?
#Module({
imports: [ /* the Module containing dependency */ ]
})
But as from my understanding I am doing everything right regards import/export of Modules, no circular dependency and so on. Here are my modules:
App
import { Module } from '#nestjs/common';
import { EurekaModule } from './eureka/eureka.module';
import { HeicModule } from './heic/heic.module';
#Module({
imports: [HeicModule, EurekaModule],
})
export class AppModule {}
Config
import { Module } from '#nestjs/common';
import { ConfigService } from './config.service';
#Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {}
ConfigService
import { Injectable } from '#nestjs/common';
import { Config } from './config.interface';
#Injectable()
export class ConfigService {
private readonly map: Config;
Redis
import { CacheModule, Module } from '#nestjs/common';
import { ClientsModule, Transport } from '#nestjs/microservices';
import { ConfigModule } from '../config/config.module';
import { ConfigService } from '../config/config.service';
import { RedisCacheService } from './redis-cache.service';
import * as redisStore from 'cache-manager-redis-store';
import { RedisPublishService } from './redis-publish.service';
#Module({
imports: [
CacheModule.register({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
store: redisStore,
host: configService.get('host'),
port: configService.get('port'),
keyPrefix: configService.get('keyPrefix'),
userName: configService.get('username'),
password: configService.get('password'),
ttl: configService.get('cacheTTL'),
}),
}),
ClientsModule.register([
{
name: 'PUBLISH_SERVICE',
transport: Transport.REDIS,
options: {
url: 'redis://localhost:6379',
},
},
]),
],
providers: [RedisCacheService, RedisPublishService],
exports: [RedisCacheService, RedisPublishService],
})
export class RedisModule {}
Redis Pub/Sub Service
import { Inject, Injectable } from '#nestjs/common';
import { ClientProxy } from '#nestjs/microservices';
import { ImageMessage } from './ImageMessage';
#Injectable()
export class RedisPublishService {
private readonly CHANNEL: string = 'heic-image-result';
constructor(#Inject('PUBLISH_SERVICE') private client: ClientProxy) {}
async publishMessage(imageMessage: ImageMessage) {
this.client.emit({ cmd: this.CHANNEL }, imageMessage);
}
}
Redis Cache Service
import { CACHE_MANAGER, Inject, Injectable } from '#nestjs/common';
import { Cache } from 'cache-manager';
import { ImageMessage } from './ImageMessage';
#Injectable()
export class RedisCacheService {
constructor(#Inject(CACHE_MANAGER) private readonly cache: Cache) {}
async get(key): Promise<ImageMessage> {
return this.cache.get(key);
}
async set(key, value) {
await this.cache.set(key, value, 120);
}
}
Heic
import { Module } from '#nestjs/common';
import { RedisModule } from '../redis/redis.module';
import { HeicService } from './heic.service';
import { MessageListenerController } from './message-listener.controller';
#Module({
imports: [RedisModule],
controllers: [MessageListenerController],
providers: [HeicService],
exports: [HeicService],
})
export class HeicModule {}
Service
import { Inject, Injectable } from '#nestjs/common';
import { ImageMessage } from '../redis/ImageMessage';
import { RedisCacheService } from '../redis/redis-cache.service';
import { RedisPublishService } from '../redis/redis-publish.service';
import { OutputFormatEnum } from './output-format.enum';
// eslint-disable-next-line #typescript-eslint/no-var-requires
const convert = require('heic-convert');
#Injectable()
export class HeicService {
constructor(
#Inject() private readonly redisPublishService: RedisPublishService,
#Inject() private readonly redisCache: RedisCacheService,
) {}
Anyone an idea what I am doing wrong?
You're using #Inject() in your constructors with no injection token. You should be passing the injection token you are wanting to inject here. The HeicService's constructor would then look something like this:
#Injectable()
export class HeicService {
constructor(
#Inject(RedisPublishService) private readonly redisPublishService: RedisPublishService,
#Inject(RedisCacheService) private readonly redisCache: RedisCacheService,
) {}
}
The other option, as you're already using classes for the RedisCacheService and RedisPublishService is to just remove the #Inject() decorators all togehter for the HeicService

Nest js. Using firstValueFrom for send in ClientKafka not async

I am using #nestjs/microservices for work with kafka in microservices. And ClientKafka one of them. The method "send" returning Observable, but I wanna Promise, so I used rxjs/firstValueFrom to get Promise from Observable. But when I await it, I don't get async.
import { HttpException, Inject, Injectable } from "#nestjs/common";
import { ClientKafka } from "#nestjs/microservices";
import { firstValueFrom } from "rxjs";
import { AssetGetOneDto } from "./dto/asset.get.one.dto";
import { AssetTransferDto } from "./dto/asset.transfer.dto";
#Injectable()
export class AssetService {
constructor(
#Inject("ASSET_SERVICE")
protected readonly assetClient: ClientKafka
){}
async onModuleInit() {
this.assetClient.subscribeToResponseOf('asset.get.one')
await this.assetClient.connect()
}
async onModuleDestroy() {
await this.assetClient.close();
}
async oneAsset(assetGetOneDto: AssetGetOneDto) {
const response = await firstValueFrom(this.assetClient.send('asset.get.one', assetGetOneDto))
console.log("response=",response)
if(response.status == 200){
return response.message;
}
return undefined;
}
}
Client creating:
import { Module } from "#nestjs/common";
import { ClientsModule, Transport } from "#nestjs/microservices";
import { getuid } from "process";
import { ConfigModule } from "src/config/config.moudle";
import { ConfigService } from "src/config/config.service";
import { AssetService } from "./asset.service";
#Module({
imports: [
ClientsModule.registerAsync([
{
name: "ASSET_SERVICE",
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
transport: Transport.KAFKA,
options: {
client: {
clientId: 'asset',
brokers: [configService.getBroker()]
},
consumer: {
groupId: 'asset-consumer-'+process.pid,
},
}
}),
}
])
],
providers: [AssetService],
exports: [AssetService]
})
export class AssetModule {}

NestJS with Typeorm can't resolve dependencies

Problem
NestJS with Typeorm dependancy error.
Description
When I insert #InjectRepository into my Session Repository class (session.repository.ts), the can't dependency errors occur. However, if I remove the #InjectRepository the error disappear and the application works fine.
Error
File: session.repository.ts
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { Session } from './session.entity';
#Injectable()
export class SessionRepository {
constructor(
// When I try to inject repository the error occur.
// When I remove the inject repository, no error occur.
#InjectRepository(Session)
private readonly session: Repository<Session>,
) {}
async test() {
return await this.session.find();
}
}
File: session.service.ts
import { Injectable } from '#nestjs/common';
import { ISessionConfig } from 'src/common/config/config.interface';
import { ConfigService } from 'src/common/config/config.service';
import { createHashedToken, getExpiresTime } from '../auth.helper';
import { SessionRepository } from './session.repository';
#Injectable()
export class SessionService {
private bytes: number;
private ttl: number;
constructor(
private readonly configService: ConfigService,
private readonly sessionRepository: SessionRepository,
) {
const sessionConfig = this.configService.get<ISessionConfig>('session');
this.bytes = sessionConfig.bytes;
this.ttl = sessionConfig.ttl;
}
getConfig(): ISessionConfig {
return { bytes: this.bytes, ttl: this.ttl };
}
async generateRefreshToken() {
const refreshToken = await createHashedToken(this.bytes);
const expires = getExpiresTime(this.ttl).toString();
return { refreshToken, expires };
}
}
File: session.module.ts
import { Module } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { Session } from './session.entity';
import { SessionRepository } from './session.repository';
import { SessionService } from './session.service';
#Module({
imports: [TypeOrmModule.forFeature([Session])],
providers: [SessionService, SessionRepository],
exports: [SessionService, SessionRepository],
})
export class SessionModule {}
File: auth.module.ts
import { Module } from '#nestjs/common';
import { AuthenticationModule } from './authentication/authentication.module';
import { JwtModule } from './jwt/jwt.module';
import { SessionModule } from './session/session.module';
#Module({
imports: [AuthenticationModule, JwtModule, SessionModule],
})
export class AuthModule {}
File: database.module.ts
import { Module } from '#nestjs/common';
import { TypeOrmModule, TypeOrmModuleOptions } from '#nestjs/typeorm';
import { IDatabaseConfig } from '../config/config.interface';
import { ConfigService } from '../config/config.service';
#Module({
imports: [
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService): TypeOrmModuleOptions => {
const dbConfig = configService.get<IDatabaseConfig>('database');
return {
type: 'mysql',
host: dbConfig.host,
port: dbConfig.port,
username: dbConfig.username,
password: dbConfig.password,
database: dbConfig.database,
entities: ['dist/**/*.entity{.ts,.js}'],
autoLoadEntities: true,
synchronize: true,
};
},
}),
],
})
export class DatabaseModule {}
File: app.module.ts
import { MiddlewareConsumer, Module, NestModule } from '#nestjs/common';
import { ConfigModule } from './common/config/config.module';
import { DatabaseModule } from './common/database/database.module';
import { HttpLoggerMiddleware } from './common/middlewares/http-logger.middleware';
import { AuthModule } from './modules/auth/auth.module';
import { PostsModule } from './modules/posts/posts.module';
import { UsersModule } from './modules/users/users.module';
#Module({
imports: [AuthModule, ConfigModule, DatabaseModule, PostsModule, UsersModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(HttpLoggerMiddleware).forRoutes('*');
}
}
I tried to solve the issue but, at the end, I am not able to solve it.
Please help me find the issue. Thank you.
Updated
I found out the problem, it is the naming issue. I changed the class name from SessionRepository -> SessionsRepository.

Resources