NestJs, RabbitMq, CQRS & BFF: Listening for event inside the bff - nestjs

I'm about to implement a Microservice Architecture with CQRS Design Pattern. The Microservices are communicating with RMQ.
Additionally, I'm adding a BFF for my Application UI.
In this scenario, the BFF needs to listen to certain domain events.
For instance: After the user sends an request to the BFF, the BFF calls a method of a Microservice which invokes an asynchronous event.
The events result will go back to the BFF and then to the user.
I'm thinking of different ways I might be able to implement this and I came up with this concept:
// BFF Application: sign-up.controller.ts
import { Controller, Post, Body } from '#nestjs/common';
import { Client, ClientProxy, Transport } from '#nestjs/microservices';
#Controller('signup')
export class SignupController {
#Client({ transport: Transport.RMQ, options: { urls: ['amqp://localhost:5672'], queue: 'signup_request' } })
client: ClientProxy;
#Post()
async signup(#Body() body: any) {
// Generate a unique identifier for the request
const requestId = uuid();
// Send the request with the unique identifier
const response = await this.client.send<any>({ cmd: 'signup', requestId }, body).toPromise();
// Wait for the SignUpEvent to be emitted before sending the response
return new Promise((resolve, reject) => {
this.client.subscribe<any>('signup_response', (response: any) => {
// Check the unique identifier to ensure the event corresponds to the original request
if (response.requestId === requestId) {
resolve(response);
}
});
});
}
}
After sending the request with the unique identifier, the microservice will execute a sign up command:
// Microservice Application: sign-up.handler.ts
import { CommandHandler, ICommandHandler } from '#nestjs/cqrs';
import { SignUpCommand } from './commands/sign-up.command';
import { SignUpEvent } from './events/sign-up.event';
import { EventBus } from '#nestjs/cqrs';
#CommandHandler(SignUpCommand)
export class SignUpCommandHandler implements ICommandHandler<SignUpCommand> {
constructor(private readonly eventBus: EventBus) {}
async execute(command: SignUpCommand) {
// Validating the user/account aggregate
// ...
// Emit the SignUpEvent with the unique identifier
this.eventBus.publish(new SignUpEvent(user, command.requestId));
}
}
Now the Event Handler gets called:
// Microservice Application: signed-up.handler.ts
import { EventsHandler, IEventHandler } from '#nestjs/cqrs';
import { SignUpEvent } from './events/sign-up.event';
import { ClientProxy, Transport } from '#nestjs/microservices';
#EventsHandler(SignUpEvent)
export class SignUpEventHandler implements IEventHandler<SignUpEvent> {
#Client({ transport: Transport.RMQ, options: { urls: ['amqp://localhost:5672'], queue: 'signup_response' } })
client: ClientProxy;
async handle(event: SignUpEvent) {
// Persist the user
const user = await this.persistUser(event.user);
// Generate access and refresh tokens
const tokens = this.generateTokens(user);
// Emit the SignUpResponse event with a unique identifier
await this.client.emit('signup_response', { user, tokens, requestId: event.requestId });
}
}
Is this, a valid way to implement this type of behaviour?
Thank you in advance.

Related

How to excecute guard before injected provider into Scope.Request

I am working on a multi-tenant app using NestJS and I store the tenantId in the token using Jwt, I need to create a database tenant connection before I do database operations but the provider(code below) is being executed before the JwtAuthGuard but I need the guard to be executed first, Is there a way to change the order of execution?
Controller method (uses JwtAuthGuard):
#Post()
#UsePipes(new ValidationPipe())
#UseGuards(JwtAuthGuard)
create(#Body() createUserDto: CreateFruitDto) {
return this.fruitsService.create(createUserDto);
}
Passport strategy (JwtAuthGuard):
export class JwtStrategy extends PassportStrategy(Strategy) {
private logger = new Logger('JwtStrategy');
constructor(private configService: JwtConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: configService.ignoreExpiration,
secretOrKey: configService.options.secret,
});
}
async validate(payload: any) {
//injects user into req
return {
userId: payload.sub,
email: payload.email,
tenantId: payload.tenant,
};
}
}
Provider being injected into FruitsModule:
provide: 'TENANT_CONTEXT',
scope: Scope.REQUEST,
inject: [REQUEST],
useFactory: (req: Request): ITenantContext => {
const { user } = req as any;
Logger.log(user); // is undefined
const tenantContext: ITenantContext = {
user.tenantId,
};
return tenantContext;
},
IMHO best to avoid request scoped providers. That should have never been introduced in Nest. That scope bubbles up and makes everything above it request scoped as well.
You could introduce middleware to work around this. Middlewares are executed before guards. The auth guard validates and extracts data from the JWT token and stores it on req.user. Configure a middleware to prepare a user property on the request. Its setter will be executed when the auth guard sets the user property on the request and it will extract the tenant ID for you.
interface ExecutionMetadata {
tenantId?: number;
}
export class TenantContextMiddleware implements NestMiddleware {
public async use(req: Request, res: Response, next: NextFunction) {
this.metadata: ExecutionMetadata = { tenantId: req.user?.tenantId };
Object.definePropery(req, 'user', {
set(user) {
this._user = user;
this.metadata.tenantId = user?.tenantId;
},
get() {
return this._user;
}
});
next();
}
}
Here I extract the tenant ID from the req.user and store it on the req.metadata property.
Using the createParamdecorator() function from NestJS you could then write a simple parameter decorator to inject this metadata.
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const Metadata = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.metadata;
},
);
You can then use this decorator to inject this metadata into your controller.
#Controller('cats')
export class CatsController {
#Get()
findAll(#Metadata() metadata: ExecutionMedata): string {
...
}
}
Remark: This decorator will only work for controller methods! NestJS is able to resolve the value for you at that stage of the request. Similar to the #Body(), #Param(), #Query()...decorators. Then you can pass this metadata down as an argument. Or you could do something fancy and setup asynchronous context tracking.

web sockets rooms not works in nestjs

I'm creating a simple chat app with rooms and I meet the problem, that, seems like socket.io can not join the room, I have schemas in mongodb, those are users, messages and rooms, for room id I provide the id of rooms schema and then want to join it with one event for example, I'm logging in from react app, then by user id I'm finding all rooms that contains my id and then want to join them all.
For the second step I want to send message and I'm targeting the room that I want send the message, with .to(roomId).emit(..., ...)
but all of this tries are useless, it not works
here is the nestjs gateway code:
import { Logger } from '#nestjs/common';
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
OnGatewayDisconnect,
OnGatewayInit,
OnGatewayConnection,
MessageBody,
ConnectedSocket,
WsResponse,
} from '#nestjs/websockets';
import { Socket, Server } from 'socket.io';
import { UserService } from 'src/user/user.service';
import { ChatService } from './chat.service';
import { CreateChatDto } from './dto/create-chat.dto';
import { UpdateChatDto } from './dto/update-chat.dto';
#WebSocketGateway({
cors: {
origin: '*',
},
})
export class ChatGateway
implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit
{
constructor(
private readonly chatService: ChatService,
private userService: UserService,
) {}
private readonly logger: Logger = new Logger(ChatGateway.name);
#WebSocketServer() server: Server;
afterInit(client: Socket) {
this.logger.log('Initialized SocketGateway');
}
handleConnection(client: Socket) {
this.logger.log(`[connection] from client (${client.id})`);
}
handleDisconnect(client: Socket) {
this.logger.log(`[disconnection] from client(${client.id})`);
}
//this works when user logs is
#SubscribeMessage('setup')
async handleJoinRoom(client: Socket, #MessageBody() userData) {
//here I'm getting all rooms that contains my user id
const rooms = await this.userService.findAll(userData);
await this.logger.log(
`[joinWhiteboard] ${userData}(${client.id}) joins ${userData}`,
);
await rooms.map((item) => {
client.join(item._id.toString());
//joining all rooms that I'm in
client.to(item._id.toString()).emit('joined');
});
}
#SubscribeMessage('new message')
create(client: Socket, #MessageBody() recievedMessage) {
this.logger.log(
`[sent a new message] from (${client.id}) to ${recievedMessage.chatRoomId}`,
);
//sending message to the room
client
.to(recievedMessage.chatRoomId)
.emit('message recieved', recievedMessage);
}
}
in my github is the full code(react part also), please feel free if you need to see it
https://github.com/Code0Breaker/chat

NestJs #Sse - event is consumed only by one client

I tried the sample SSE application provided with nest.js (28-SSE), and modified the sse endpoint to send a counter:
#Sse('sse')
sse(): Observable<MessageEvent> {
return interval(5000).pipe(
map((_) => ({ data: { hello: `world - ${this.c++}` }} as MessageEvent)),
);
}
I expect that each client that is listening to this SSE will receive the message, but when opening multiple browser tabs I can see that each message is consumed only by one browser, so if I have three browsers open I get the following:
How can I get the expected behavior?
To achieve the behavior you're expecting you need to create a separate stream for each connection and push the data stream as you wish.
One possible minimalistic solution is below
import { Controller, Get, MessageEvent, OnModuleDestroy, OnModuleInit, Res, Sse } from '#nestjs/common';
import { readFileSync } from 'fs';
import { join } from 'path';
import { Observable, ReplaySubject } from 'rxjs';
import { map } from 'rxjs/operators';
import { Response } from 'express';
#Controller()
export class AppController implements OnModuleInit, OnModuleDestroy {
private stream: {
id: string;
subject: ReplaySubject<unknown>;
observer: Observable<unknown>;
}[] = [];
private timer: NodeJS.Timeout;
private id = 0;
public onModuleInit(): void {
this.timer = setInterval(() => {
this.id += 1;
this.stream.forEach(({ subject }) => subject.next(this.id));
}, 1000);
}
public onModuleDestroy(): void {
clearInterval(this.timer);
}
#Get()
public index(): string {
return readFileSync(join(__dirname, 'index.html'), 'utf-8').toString();
}
#Sse('sse')
public sse(#Res() response: Response): Observable<MessageEvent> {
const id = AppController.genStreamId();
// Clean up the stream when the client disconnects
response.on('close', () => this.removeStream(id));
// Create a new stream
const subject = new ReplaySubject();
const observer = subject.asObservable();
this.addStream(subject, observer, id);
return observer.pipe(map((data) => ({
id: `my-stream-id:${id}`,
data: `Hello world ${data}`,
event: 'my-event-name',
}) as MessageEvent));
}
private addStream(subject: ReplaySubject<unknown>, observer: Observable<unknown>, id: string): void {
this.stream.push({
id,
subject,
observer,
});
}
private removeStream(id: string): void {
this.stream = this.stream.filter(stream => stream.id !== id);
}
private static genStreamId(): string {
return Math.random().toString(36).substring(2, 15);
}
}
You can make a separate service for it and make it cleaner and push stream data from different places but as an example showcase this would result as shown in the screenshot below
This behaviour is correct. Each SSE connection is a dedicated socket and handled by a dedicated server process. So each client can receive different data.
It is not a broadcast-same-thing-to-many technology.
How can I get the expected behavior?
Have a central record (e.g. in an SQL DB) of the desired value you want to send out to all the connected clients.
Then have each of the SSE server processes watch or poll that central record
and send out an event each time it changes.
you just have to generate a new observable for each sse connection of the same subject
private events: Subject<MessageEvent> = new Subject();
constuctor(){
timer(0, 1000).pipe(takeUntil(this.destroy)).subscribe(async (index: any)=>{
let event: MessageEvent = {
id: index,
type: 'test',
retry: 30000,
data: {index: index}
} as MessageEvent;
this.events.next(event);
});
}
#Sse('sse')
public sse(): Observable<MessageEvent> {
return this.events.asObservable();
}
Note: I'm skipping the rest of the controller code.
Regards,

How to create common class for third-party API requests in NestJS

I am creating NestJS application where I am making third-party API requests. For that I have to write the same thing inside every function in order to get the data.
To make things non-repeating, how can I write on common class that has API request based on GET or POST request and send the response so that I can use that class in every function.
Below is my code:
subscribe.service.ts
#Injectable()
export class SubscribeService {
constructor(#InjectModel('Subscribe') private readonly model:Model<Subscribe>,
#Inject(CACHE_MANAGER) private cacheManager:Cache,
private httpService: HttpService){}
async addSubscriber(subscriberDto:SubscribeDto){
const url = 'https://track.cxipl.com/api/v2/phone-tracking/subscribe';
const headersRequest = {
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
};
try{
const resp = await this.httpService.post(url,subscriberDto,{ headers: headersRequest }).pipe(
map((response) => {
if(response.data.success == true){
const data = new this.model(subscriberDto);
// return data.save();
const saved = data.save();
if(saved){
const msgSuccess = {
"success":response.data.success,
"status":response.data.data.status
}
return msgSuccess;
}
}
else{
const msgFail = {"success":response.data.success}
return msgFail;
}
}),
);
return resp;
}
catch(err){
return err;
}
}
async getLocation(phoneNumber:PhoneNumber){
try{
const location = await this.cacheManager.get<Coordinates>(phoneNumber.phoneNumber);
if(location){
return location;
}
else{
const resp = await axios.post('https://track.cxipl.com/api/v2/phone-tracking/location',phoneNumber,{headers:{
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
}});
const msg:Coordinates = {
"location":resp.data.data.location,
"timestamp":resp.data.data.timestamp
}
await this.cacheManager.set<Coordinates>(phoneNumber.phoneNumber,msg, { ttl: 3600 });
return msg;
}
}
catch(err){
console.log(err);
return err;
}
}
}
As in above code in both function addSubscriber() and getLocation() I need to hit the API repeatedly and add request headers again and again is there any way so that I can create one separate class for request and response and utilize in my service.
How can I achieve desired the result?
To create a common class for making third-party API requests in NestJS, you can follow these steps:
Create a new file in your NestJS project to store the common class.
For example, you could create a file called api.service.ts in the
src/common directory.
In the file, create a new class called ApiService that will be responsible for making the API requests. This class should have a
constructor that injects the necessary dependencies, such as the
HttpService provided by NestJS.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
}
Add methods to the ApiService class for each type of API request you want to make. For example, you might have a get() method for making GET requests, a post() method for making POST requests, and so on. Each method should accept the necessary parameters for making the request (such as the URL and any query parameters or request body), and use the HttpService to make the request.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
async get(url: string, params?: object): Promise<any> {
return this.httpService.get(url, { params }).toPromise();
}
async post(url: string, body: object): Promise<any> {
return this.httpService.post(url, body).toPromise();
}
}
Inject the ApiService wherever you need to make API requests. For example, you might inject it into a service or a controller, and use the methods of the ApiService to make the actual API requests.
import { Injectable } from '#nestjs/common';
import { ApiService } from './api.service';
#Injectable()
export class SomeService {
constructor(private readonly apiService: ApiService) {}
async getData(): Promise<any> {
return this.apiService.get('https://some-api.com/endpoint');
}
}
This is just one way you could create a common class for making third-party API requests in NestJS. You can customize the ApiService class to meet the specific needs of your application

angular-nestjs socket.io doesn't update if page doesn't refresh

I am using Angular and NestJS with socket.io to create a chat application.
When I started saving data on my db, my Angular side stopped updating messages unless page refreshes.
I couldn't find a way to pass the userid on the server gateway in order to search in the db only chat of the user.
Angular service
sendMessage(chat: Chat): void {
this.socket.emit('sendMessage', chat)
} //using it when a message is sent
getNewMessage(): Observable<Chat[]> {
return this.socket.fromEvent<any>('lastChats');
} //using it onInit
sendId(userId: number): void {
this.socket.emit('loadMessages', userId);
} //using it onInit
NestJS Gateway
#WebSocketGateway({ cors: { origin: ['http://localhost:4200'] } })
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
constructor(
#InjectRepository(Chat) private chatRepository: Repository<Chat>,
) { }
#WebSocketServer()
server: Server
async handleConnection(client: any, ...args: any[]) {
const chats = await this.chatRepository.createQueryBuilder('chat')
.innerJoinAndSelect('chat.ally', 'ally')
.innerJoinAndSelect('chat.talent', 'talent')
.getMany();
this.server.emit('lastChats', chats)
}
handleDisconnect(client: any, ...args: any[]) {
console.log("Disconnected")
}
#SubscribeMessage('sendMessage')
handleMessage(socket: Socket, chat: Chat) {
if (chat.messagesJSON && chat.messagesJSON.trim() !== '') {
this.chatRepository.save(chat)
this.server.emit('newChat', chat)
}
#SubscribeMessage('loadMessages')
async handleLoad(socket: Socket, id: number) {
const chats = await this.chatRepository.createQueryBuilder('chat')
.innerJoinAndSelect('chat.ally', 'ally')
.innerJoinAndSelect('chat.talent', 'talent')
.where('ally = :user OR talent = :user', { user: id })
.getMany();
this.server.emit('lastChats', chats)
}
}
To send from angular to nestjs you can use
Angular:
this.socket = io(environment.SOCKET_URL, {
extraHeaders: {Authorization: localStorage.getItem('token')},
});
Nestjs: in the handleConnexion
Const jwt =socket.handshake.headers.authorization
I think you can replace the token by usrerId

Resources