EventPattern handler is not being triggered outside controllers - node.js

I'm trying to setup a NestJS Hybrid Application that listens to a Redis service for events and triggers a handler in my code. I've defined a publisher that publishes events via ClientProxy.emit() as shown in the documentation.
EventPublisher.ts
#Injectable()
export class EventPublisher implements IEventPublisher {
constructor(#Inject('redisClient') private client: ClientProxy) {}
async publish<T extends IEvent = IEvent>(event: T) {
await this.client.emit('a', JSON.stringify({event})).toPromise();
}
}
I'm injecting my ClientProxy via my module class
const redisClient = ClientsModule.register([
{ name: 'redisClient', transport: Transport.REDIS, options: {
url: 'redis://localhost:6379'}
}
])
#Module({
imports: [
redisClient
],
... Other setups
})
export class FooModule {};
To listen to incoming events I created a microservice that uses redis as its transport.
main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
const redisMicroservice = app.connectMicroservice<RedisOptions>({
transport: Transport.REDIS,
options: {
url: 'redis://localhost:6379'
}
})
await app.startAllMicroservicesAsync();
await app.listen(process.env.PORT || 3000);
}
This setup works fine as I am recieving the send data in my terminal when I subscirbe to 'a' using the redis-cli. I've tried defining eventhandlers on multiple classes in my code, but none of them are triggered when an event is emitted to Redis unless I create a handler that is located in a controller.
controller.ts
#EventPattern('a')
doSomething() {
console.log('success')
}
In the documentation I can't find any mention of eventhandlers having to be in a controller, but that seems to be the only location that triggers the handlers right now. Would anyone know what could be causing this? Any help is really appreciated.

I came to the same realisation yesterday. I tried to look for an explanation in the documentation, but as often with NestJS, I found none. #Decorators make it hard to investigate as they hide their logic.
It seems indeed that #EventPattern() will only be triggered if placed inside a #Controller(), but I have yet to understand why.

Related

Nest.JS test EventEmitter Handler with Jest

I'm developing a backend API with Nest.JS which works great so far.
Recently I added tests with Jest and it works good.
However, I came across a problem and I do not understand how to solve this issue.
I use EventEmitter2 to emit and handle events. So basically on a service function, I do some action on the database and emit a custom event afterwards, so another handler can do something.
This works like a charm, when I call the API. However, it does not work inside the test environment.
I don't get any errors, everything seems to work, but the event is not being consumed somehow, when inside the test (console.log is not appearing, but when called from "outside" API call it works as expected).
// the same test function calls the function that will emit an event
// i add delay to ensure the event can be consumed in the meantime
jest.setTimeout(30000);
it('should contain metadata', async () => {
await new Promise((r) => setTimeout(r, 5000));
const response = await controller.findAll(testAccount);
// it should contain metadata, but it is still undefined, so event is not consumed
This is the test setup:
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
EventEmitterModule.forRoot(),
MyModule,
//..
],
}).compile();
this.eventEmitter.emit('event', { data });
// the consumer
#OnEvent('event')
async handleEvent

Messages not reaching handler while using WebSocket and nestjs

I am trying to implement WebSocket into nestjs app and I have problems with messages not reaching my handler in nestjs.
I am able to connect both ends and send message from nestjs to client but not the other way.
this is my nestjs code: (please note that i am not using socket.io i am implementing ws as WebSocket
import {
OnGatewayInit,
WebSocketGateway,
WebSocketServer,
} from '#nestjs/websockets';
import { Logger } from '#nestjs/common';
#WebSocketGateway(5015)
export class ExportFeedsGateway implements OnGatewayInit {
#WebSocketServer() wss: any;
private logger: Logger = new Logger('ExportFeedsGateway');
afterInit(server: any): any {
this.logger.log('Export Feeds Initialized');
}
handleConnection(client) {
client.send('message from server'); // this message is properly send and read on the client side
}
handleMessage(message) {
console.log('message', message); // this is never logged in nest
}
}
and some client code:
const WebSocket = require( 'ws');
...
this.ws = new WebSocket('ws://localhost:5015');
this.ws.on('open', () => {
this.ws.send('message from client') // this message never reaches server
});
...
Question is what is the problem with nestjs messages handler that it doesnt pick up messages?
You're missing the #SubscribeMessage('message') that you'll need for Nest to register the handler to the websocket server. handleConnection is a bit of a special case method as defined inthe lifecycle methods. Also, when sending the message from the ws library, use the form: ws.send('{"event": "message", "data": whateverDataObjectYouWant }', callback). This way, Nest can parse the event and properly route it

Shouldn't NestJS exception filters catch event emitter errors?

I'm running some manual tests on a NestJS application I'm refactoring and just found an odd behaviour from nest's global exception filter.
The developer who created this project used this as a exception filter:
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '#nestjs/common';
import { HttpAdapterHost } from '#nestjs/core';
import { LoggerService } from '../../config/logger.service';
#Catch()
export class GlobalExceptionsFilter implements ExceptionFilter {
constructor(
private readonly httpAdapterHost: HttpAdapterHost,
private readonly logger: LoggerService,
) {}
catch(exception: Error, host: ArgumentsHost): void {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const httpStatus =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const responseBody = {
statusCode: httpStatus,
path: httpAdapter.getRequestUrl(ctx.getRequest()),
cause: `${exception.name}: ${exception.message}`,
timestamp: new Date().toISOString(),
};
this.logger.error(responseBody?.cause);
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}
}
Which is almost exactly the same catch-everything filter example in Nest's docs, so it's very generic, indeed.
In a certain part of this project, there's a controller with something like this:
#OnEvent(Events.POTATO_EVENT)
async potatoMethod(payload) {
return await this.service.potatoMethod(payload);
Currently, there's no payload validation on the service's potatoMethod, so the controller receives a payload from another service using EventEmitter2 and forwards it to it's service, which then tries to fetch a record on the database by calling an ORM method.
If payload.potatoNumber is sent correctly, nothing wrong happens, but if I send payload.potatoNumber as undefined, the ORM will throw an error that will not be caught on that global filter.
The same thing doesn't happen if instead of using a #OnEvent() I just use a #Get() directly on the controller to turn it into an endpoint.
Currently, main looks something like this:
(async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
})();
I might be completely wrong, but this should only happen if the application is not using NestFactory.createMicroservice() on it's main.ts file, right? If positive, how exactly does microservices work natively in Nest?

How to handle when client disconnects on NestJS

I am working with NestJS and I need to know when a client has forced the disconnection or has canceled it. (either by mistake or because they wanted to).
For exaple, in Express it's as easy as:
const express = require('express')
const app = express()
const port = 3000
app.get('/', (expressRequest, expressResponse) => {
// Detecting close event
expressRequest.on('close', function() {
console.log('Client connection closed....!');
});
// Detecting end event
expressRequest.on('end', function() {
console.log('Client connection end....!');
});
expressResponse.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
The question is: working with NestJS, what is the correct way to do it?
The first thing I would try is using the #Req() param decorator. Assuming you're using Nests default Express adapter, then the request object received is the Express req object.
The following should work for you. The rest of this post is just cleaning it up and making it more "Nest".
import { Controller, Get, Req } from '#nestjs/common';
import { Request } from 'express';
#Controller()
export class AppController{
#Get()
test(#Req() req: Request): string {
req.on('close', () => console.log('Doing something with closed connection'))
return "Hello, world!"
}
}
If you're planning to reuse this logic in a few controller methods, then I would also consider creating a custom decorator for it:
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
import { Observable } from 'rxjs';
import { Request } from 'express';
export const OnConnectionClosed = createParamDecorator(
(data: unknown, ctx: ExecutionContext) =>
new Observable((observer) => {
const request = ctx.switchToHttp().getRequest<Request>();
request.on('close', () => observer.complete());
}),
);
And then using it like the following:
#Controller()
export class AppController{
#Get()
test(#OnConnectionClosed() onClosed: Observable<void>): string {
onClosed.subscribe({
complete: () => console.log('Connection closed'),
});
return 'Hello, world!';
}
}
And with that, you've created your own "Nest" way to listen for close events on the incoming request.
Nestjs has many different components that are executed at different times during the life cycle of a request.
The order in which these components are executed would be the following
NestJs request Life cycle
Incoming request
Globally bound middleware
Module bound middleware
Global guards
Controller guards
Route guards
Global interceptors (pre-controller)
Controller interceptors (pre-controller)
Route interceptors (pre-controller)
Global pipes
Controller pipes
Route pipes
Route parameter pipes
Controller (method handler)
Service (if exists)
Route interceptor (post-request)
Controller interceptor (post-request)
Global interceptor (post-request)
Exception filters (route, then controller, then global)
Server response**
The answer to your question:
I think it should be detected in the following points
Global interceptor (post-request)
Controller interceptor (post-request)

Network error: Failed to fetch on ionic react

it's the first time that i post on forums,
I really needs your help.
I'm stuck with a problem, I have an Ionic/React application, a Node.js application and a graphQL/Apollo API,
when i'm calling the graphql API from my browser it's all working fine but when i'm building the app with capacitor and running it on my Android device, I get "Network error: Failed to fetch".
Here is my client.ts code where i'm setting up my ApolloClient
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache, NormalizedCacheObject } from 'apollo-cache-inmemory';
class RequestUtils {
clientApollo: any;
constructor() {
const httpLink = createHttpLink({
uri: 'http://192.168.1.86:4000/graphql'
});
const authLink = setContext((req, { headers }) => ({
headers: {
...headers
}
}));
this.clientApollo = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
}
}
const requestUtils = new RequestUtils();
const client: ApolloClient<NormalizedCacheObject> = requestUtils.clientApollo;
export default client;
and here is my graphql.ts
import resolvers from "../resolvers/index";
import { ApolloServer, AuthenticationError } from "apollo-server-express";
import { getModels } from "../models/index";
import schemas from "../schemas/index";
import * as express from "express";
import * as cors from "cors";
import { connectToMongo } from "./config";
import { info, success, warning, error } from "./logger";
export class query {
public app: any;
constructor() {
const models = getModels();
this.app = express();
this.app.use(cors());
try {
info("Starting connection to graphql");
const server = new ApolloServer({
typeDefs: schemas,
resolvers,
context: async ({ req }) => {
if (req) {
return {
models: models
};
}
}
});
this.app = express();
server.applyMiddleware({ app: this.app, path: "/graphql" });
success(`Connected to graphql`);
} catch (error) {
warning(error);
}
this.app.listen(4000, () => {
connectToMongo();
});
}
}
I think that it's a problem with something like cors or ip adress but i don't find any solution to my problem.
I hope someone can help me !
EDIT:
I tried to run my node server on another computer and cal the graphql API from my main computer with my ionic react react webapp. The origin is in fact different but there is no error, all works perfectly. But with my builded app on my android device, always same error.
So, I think it's not cors, maybe it's something with Capacitor/cordova, or something like this.
At first, i thought android app wasn't allowed to connect to network, I checked and it's connected, but I'm not sure.
If someone could help me, it would be very sympathic , I'm really stuck with this, my app is useless if i can't connect to server en database XD
Had exactly the same problem.
It looks like the cause is that Android forbids HTTP requests by default.
The following fix worked for me:
To allow http requests, I added android:usesCleartextTraffic="true" to my app's AndroidManifest.xml
Solution post
I "solved" my problem by launching my app with " ionic capacitor run android -l --external "
I don't what will happens if the app is in production but at least I can progress in my dev.
I'll see later what happens ¯_(ツ)_/¯

Resources