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 {}
Related
I'm trying to implement JWT with NestJs. In my user.module.ts, I have added following configuration:
import { Module } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { UserService } from './user.service';
import { UserResolver } from './user.resolver';
import { User } from './entities/user.entity';
import { TypeOrmModule } from '#nestjs/typeorm';
import { AuthHelper } from './auth/auth.helper';
import { JwtStrategy } from './auth/auth.strategy';
import { PassportModule } from '#nestjs/passport';
import { JwtModule } from '#nestjs/jwt';
#Module({
imports: [
// PassportModule.register({ defaultStrategy: 'jwt', property: 'user' }),
// JwtModule.registerAsync({
// inject: [ConfigService],
// useFactory: (config: ConfigService) => ({
// secret: 'secret',
// signOptions: { expiresIn: 36000000 },
// }),
// }),
TypeOrmModule.forFeature([User]),
],
providers: [UserResolver, UserService], // AuthHelper, JwtStrategy],
})
export class UserModule {}
Whenever I uncomment these lines, I get some issues.
Here are some relevant files:
auth.strategy.ts
import { Injectable, Inject } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { User } from '../entities/user.entity';
import { AuthHelper } from './auth.helper';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
#Inject(AuthHelper)
private readonly helper: AuthHelper;
constructor(#Inject(ConfigService) config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'KhubSecret',
ignoreExpiration: true,
});
}
private validate(payload: string): Promise<User | never> {
return this.helper.validateUser(payload);
}
}
auth.guard.ts
import { Injectable, ExecutionContext } from '#nestjs/common';
import { AuthGuard, IAuthGuard } from '#nestjs/passport';
import { User } from '../entities/user.entity';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') implements IAuthGuard {
public handleRequest(err: unknown, user: User): any {
return user;
}
public async canActivate(context: ExecutionContext): Promise<boolean> {
await super.canActivate(context);
const { user } = context.switchToHttp().getRequest();
return user ? true : false;
}
}
auth.helper.ts:
import {
Injectable,
HttpException,
HttpStatus,
UnauthorizedException,
} from '#nestjs/common';
import { JwtService } from '#nestjs/jwt';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcryptjs';
import { User } from '../entities/user.entity';
#Injectable()
export class AuthHelper {
#InjectRepository(User)
private readonly repository: Repository<User>;
private readonly jwt: JwtService;
constructor(jwt: JwtService) {
this.jwt = jwt;
}
public async decode(token: string): Promise<unknown> {
return this.jwt.decode(token, null);
}
public async validateUser(decoded: any): Promise<User> {
return this.repository.findOne(decoded.id);
}
public generateToken(user: User): string {
return this.jwt.sign({
id: user.userId,
username: user.username,
});
}
public isPasswordValid(password: string, userPassword: string): boolean {
return bcrypt.compareSync(password, userPassword);
}
public encodePassword(password: string): string {
const salt: string = bcrypt.genSaltSync(10);
return bcrypt.hashSync(password, salt);
}
private async validate(token: string): Promise<boolean | never> {
const decoded: unknown = this.jwt.verify(token);
if (!decoded) {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
const user: User = await this.validateUser(decoded);
if (!user) {
throw new UnauthorizedException();
}
return true;
}
}
I get some error like this:
[Nest] 18360 - 05/10/2022, 18:14:42 ERROR [ExceptionHandler] Nest can't resolve dependencies of the JWT_MODULE_OPTIONS (?). Please make sure that the argument ConfigService at index [0] is available in the JwtModule context.
Potential solutions:
- If ConfigService is a provider, is it part of the current JwtModule?
- If ConfigService is exported from a separate #Module, is that module imported within JwtModule?
#Module({
imports: [ /* the Module containing ConfigService */ ]
})
Error: Nest can't resolve dependencies of the JWT_MODULE_OPTIONS (?). Please make sure that the argument ConfigService at index [0] is available in the JwtModule context.
Potential solutions:
- If ConfigService is a provider, is it part of the current JwtModule?
- If ConfigService is exported from a separate #Module, is that module imported within JwtModule?
#Module({
imports: [ /* the Module containing ConfigService */ ]
})
I have already tested and implemented these solutions:
NestJS can't resolve dependencies of the JWT_MODULE_OPTIONS
Here is the solution. I hope it would help.
In your auth strategy add this:
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'KhubSecret',
ignoreExpiration: true,
});
}
instead of:
constructor(#Inject(ConfigService) config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'KhubSecret',
ignoreExpiration: true,
});
}
In Short, Remove
#Inject(ConfigService) config: ConfigService
from your constructor
From the immediate error you've provided, I'll assume that your ConfigModule is not global. This means that you'll need to import the ConfigModule in the JwtModule.registerAsync() call (in the imports array). Another option would be to make your ConfigModule global (#nestjs/config has an option for this) so that you already have access to ConfigService.
I'm trying to create shared-module for microservices.
There are two packages:
#name/hub - ordinary HTTP-service
#name/lib - shared library
Library contains simple exception-filter module:
http.exception-filter.ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '#nestjs/common';
import { Request, Response } from 'express';
#Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message,
});
}
}
exceptions-fitlers.module.ts
import { Module, Scope } from '#nestjs/common';
import { HttpExceptionFilter } from './http.exception-filter';
import { APP_FILTER } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_FILTER,
scope: Scope.REQUEST,
useClass: HttpExceptionFilter,
},
],
})
export class ExceptionsFiltersModule {}
Service contains controller that uses this filter:
app.module.ts
import { Module } from '#nestjs/common';
import { ExceptionsFiltersModule } from '#name/nodejs-lib/dist';
#Module({
imports: [ExceptionsFiltersModule, ...],
})
export class AppModule {}
controller.ts
#Controller('app')
#UseFilters(new HttpExceptionFilter())
export class AppController{
#Post('/check')
#HttpCode(200)
async check(#Body() dto: A): Promise<B> {
throw new BadRequestException('Invalid data');
}
}
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './modules/app.module';
import { ConfigService } from '#nestjs/config';
import { DocumentationBuilder, HttpExceptionFilter } from '#name/nodejs-lib/dist';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: true });
const config = app.get(ConfigService);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(config.get<number>('HTTP_PORT'), () => {
logger.log(`HTTP Server: http://${config.get('HTTP_HOST')}:${config.get('HTTP_PORT')}`);
});
}
bootstrap().then();
Then I trying trigger this filter, I receive generic response:
{
"statusCode": 400,
"message": "Invalid data",
"error": "Bad Request"
}
If someone has opinion, please let me know. Thanks
I have a problem with overriding provider/setup module testing in nest.js application for testing.
Module file: smsModule.ts:
import { TwilioService } from './twilio/twilio.service';
import { DynamicModule, Module } from '#nestjs/common';
import { TwilioConfig, SMS_TWILIO_CONFIG } from './twilio.config';
import { TwilioClientCustom } from './twilio/twilio-client-custom';
#Module({
imports: [],
providers: [TwilioService],
})
export class SmsModule {
static register(options: TwilioConfig): DynamicModule {
return {
module: SmsModule,
imports: [HttpModule],
providers: [
{
provide: SMS_TWILIO_CONFIG,
useValue: options,
},
TwilioService,
TwilioClientCustom,
],
exports: [TwilioService],
};
}
}
Twilio client, config files:
//client
import { TwilioConfig, SMS_TWILIO_CONFIG } from '../twilio.config';
import { Twilio } from 'twilio';
import { Inject, Injectable } from '#nestjs/common';
#Injectable()
export class TwilioClientCustom extends Twilio {
constructor(#Inject(SMS_TWILIO_CONFIG) twilioConfig: TwilioConfig) {
super(twilioConfig.accountSid, twilioConfig.authToken);
}
}
//config
import { IsString, IsNotEmpty, NotContains, IsOptional, IsArray } from 'class-validator';
const INFO = 'Must be ....';
export class TwilioConfig {
#IsString()
#IsNotEmpty()
#NotContains('OVERRIDE_WITH_', { message: INFO })
accountSid: string;
#IsString()
#IsNotEmpty()
authToken: string;
#IsArray()
#IsOptional()
#IsNotEmpty('OVERRIDE_WITH_', { message: INFO })
serviceSid: string;
}
export const SMS_TWILIO_CONFIG = 'smsTwilioConfig';
Twilio service file: twilio.service.tst:
import { HttpService } from '#nestjs/axios';
import { TwilioConfig, SMS_TWILIO_CONFIG } from '../twilio.config';
import { SendSmsTwilioService } from './../sendsms.service';
import { Inject, Injectable } from '#nestjs/common';
import { TwilioClientCustom } from './twilio-client-custom';
#Injectable()
export class TwilioService implements SendSmsTwilioService {
constructor(
#Inject(SMS_TWILIO_CONFIG) private readonly config: TwilioConfig,
private readonly client: TwilioClientCustom,
private readonly httpService: HttpService
) {}
async sendSMS(to: string, from: string, body: string): Promise<string> {
......
return this.client.messages
.create({
to, //Recipinet's number
from, //Twilio number
body, //Messages to Recipient
})
.then((message) => message.sid)
.catch(() => {
throw new Error('TWILIO accountSid or authToken not valid');
});
}
I would like to test my service:
test file:
import { Test, TestingModule } from '#nestjs/testing';
//import { TWILIO_CONFIG_SPEC } from './test.config';
import { TwilioClientCustom } from '../src/twilio/twilio-client-custom';
import { HttpService } from '#nestjs/axios';
import { TwilioConfig } from './../src/twilio.config';
import { TwilioService } from './../src/twilio/twilio.service';
import nock from 'nock';
describe('TwilioService', () => {
let service: TwilioService;
let client: TwilioClientCustom;
let httpService: HttpService;
afterEach(() => {
nock.cleanAll();
});
//const smsServiceMock = {};
beforeEach(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
providers: [
TwilioService,
{
provide: HttpService,
useValue: {
method1: jest.fn(),
method2: jest.fn(),
method3: jest.fn(),
},
},
TwilioService,
],
imports: [
NestConfigModule.forRoot({
config: TwilioConfig,
} as Record<string, unknown>),
],
}).compile();
//getting service module from main module
httpService = moduleRef.get<HttpService>(HttpService);
client = moduleRef.get<TwilioClientCustom>(TwilioClientCustom);
service = moduleRef.get<TwilioService>(TwilioService);
});
//check service is avaible
it('Should be defined', () => {
expect(client).toBeDefined();
expect(service).toBeDefined();
expect(httpService).toBeDefined();
});
And after running test I get following errors:
Nest can't resolve dependencies of the TwilioService (?, TwilioClientCustom, HttpService). Please make sure that the argument smsTwilioConfig at index [0] is available in the RootTestModule context.
Potential solutions:
- If smsTwilioConfig is a provider, is it part of the current RootTestModule?
- If smsTwilioConfig is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing smsTwilioConfig */ ]
})
How can I solve this problem ?
smsTwilioConfig is registered with Nest's IOC via SmsModule.register(opts).
However, it seems you're attempting to test TwilioService directly with createTestingModule. Which is fine, but it does mean you need include the config with a provider or import in your test.
My guess is that you thought NestConfigModule... would do that, but that's not setting the config at the right level.
I would think the following is the right direction
const moduleRef: TestingModule = await Test.createTestingModule({
providers: [
TwilioService,
{
provide: HttpService,
useValue: {
method1: jest.fn(),
method2: jest.fn(),
method3: jest.fn(),
},
},
{
// added this
provide: SMS_TWILIO_CONFIG,
useValue: testConfig
},
],
imports: [
// removed NestConfigModule
],
}).compile();
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.
I am trying to add role guard in Nest JS API. I used Passport, Jwt authentication for this.
In my RolesGuard class I made the request and get the user from it to check the user role valid or not. I attached the code below.
roles.guard.ts
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user: User = request.user;
return this.userService.findOne(user.id).pipe(
map((user: User) => {
const hasRole = () => roles.indexOf(user.role) > -1;
let hasPermission: boolean = false;
if (hasRole()) {
hasPermission = true;
}
return user && hasPermission;
}),
);
}
Problem here is context.switchToHttp().getRequest() returns object, which is undefined. So I could not get user details from it.
After I had some research about this error I found that order of the decorators in controller can be the issue. Then I changed the order, but still problem appears as same. Bellow I added that code also.
user.controller.ts
#UseGuards(JwtAuthGuard, RolesGuard)
#hasRoles(UserRole.USER)
#Get()
findAll(): Observable<User[]> {
return this.userService.findAll();
}
-Thank you-
if you are using graphql you can make the changes to fit the code below
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { GqlExecutionContext } from '#nestjs/graphql';
import { Role } from 'src/enums/role.enum';
import { ROLES_KEY } from '../auth';
#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 ctx = GqlExecutionContext.create(context);
const user = ctx.getContext().req.user;
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
if you are not using graphql, make the changes to fit the code below
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
#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));
}
}
then in you controller you can do
#UseGuards(JwtAuthGuard, RolesGuard)
#hasRoles(UserRole.USER)
#Get()
findAll(): Observable<User[]> {
return this.userService.findAll();
}
finally, for all this to work add the global guard to your app.module.ts
like so
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import {MongooseModule} from '#nestjs/mongoose';
import { AppService } from './app.service';
import { ConfigModule } from '#nestjs/config';
import configuration from 'src/config/configuration';
import { RolesGuard } from 'src/auth/auth';
import { UsersModule } from '../users/users.module';
import { AdminsModule } from 'src/admin/admins.module';
import { config } from 'dotenv';
import * as Joi from 'joi';
config();
#Module({
imports: [
// other modules
UsersModule,
// configuration module
ConfigModule.forRoot({
isGlobal: true,
cache: true,
load: [configuration],
expandVariables: true,
// validate stuff with Joi
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'provision')
.default('development'),
PORT: Joi.number().default(5000),
}),
validationOptions: {
// allow unknown keys (change to false to fail on unknown keys)
allowUnknown: true,
abortEarly: true,
},
}),
// connect to mongodb database
MongooseModule.forRoot(process.env.DB_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
}),
],
controllers: [AppController],
providers: [
AppService,
{
provide: 'APP_GUARD',
useClass: RolesGuard,
}
],
})
export class AppModule {}