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)
Related
I'm quite new to NestJS, so if I overlook something obvious, please forgive me.
Now I'm implementing a simple logger which logs request and response.
In NestJS, you can put a middleware only before routing, so my middleware overwrite res.write and res.end so that response chunks will be pushed to an array in the middleware.
export class Logger implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const originalResWrite = res.write;
const originalResEnd = res.end;
const chunks = [];
res.write = (...chunk) => {
chunks.push(Buffer.from(chunk[0]));
originalResWrite.apply(res, chunk);
};
res.end = (...chunk) => {
if (chunk[0]) {
chunks.push(Buffer.from(chunk[0]));
}
const body = Buffer.concat(chunks).toString("utf8");
console.log({
requestBody: req.body,
responseBody: JSON.parse(body) || body || {},
});
originalResEnd.apply(res, chunk);
};
}
}
However, if this middleware is instantiated as a singleton and shared by all requests--like Django middleware--, chunks Array will receive chunks from several streams, and the log will be totally messed up.
So, the problem is, how comsumer.apply instantiate a midddleware.
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(Logger)
.forRoutes('*');
}
}
In NestJS, is a middleware instantiated as a singleton, or instantiated each time request comes?
If you would answer my question, I would appreciate it.
Yes, by default providers are singleton in Nestjs, however, you can define the scope for your middleware using the options in the Injectable decorator.
So you can add this before your middleware class definition
#Injectable({ scope: Scope.REQUEST })
Check out this link in the documentation and this answer on Stackoverflow.
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
In my Node.js application I´m using fastify as framework together with some plugins (i18next).
I´m using i18next for translation (is working properly in preHandler and handler hooks) and want to customize all errors by using my translations via i18next in my custom error handler (via fastify setErrorHandler method).
Here is my coding so far (from top to bottom):
import fastify from "fastify";
import routes from "./routes/routes.js";
import i18next from "./config/i18next.js";
import i18nextMiddleware from "i18next-http-middleware";
const app = fastify({
logger: true,
ajv: {
customOptions: {
allErrors: true,
$data: true
}
},
});
app.register(i18nextMiddleware.plugin, { i18next });
app.register(routes, { prefix: '/api/v1' });
app.setErrorHandler((error, request, reply) => {
console.log('request.t: ', request.t);
if (error.validation) {
// other coding ...
reply.send(error);
}
});
(async () => {
try {
await app.listen(process.env.PORT);
console.log(`Server started listening on port ${process.env.PORT}`);
} catch (err) {
app.log.error(err);
}
})();
Inside the setErrorHandler (which is sync) I want to use the initialized t() method from i18next instance passed to request object (this is working for all my routes in the preHandler and handler hooks) but is not working in the setErrorHandler, as I´ll get undefined when an error occours.
I know the setErrorHandler is sync and all plugin registration will be handled async, but didn´t solved it yet.
What I´ve also tried is to do the setErrorHandler call in the after() hook when registering the i18next plugin, but the result is the same. I know I´m missing a small detail, but any tips are appreciated, as I´m spinning my head around this since hours.
This happens because the i18next-http-middleware plugin adds the t method to the request object on a preHandler hook that is executed after the JSON validation step:
export function plugin (instance, options, next) {
const middleware = handle(options.i18next, options)
instance.addHook('preHandler', (request, reply, next) => middleware(request, reply, next))
return next()
}
source
You should be able to write a workaround like this:
import i18nextMiddleware from "i18next-http-middleware";
// ....
const middleware = i18nextMiddleware.handle({})
app.addHook('preValidation', (request, reply, next) => middleware(request, reply, next))
I think that is a bug of the module tho
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.
My service file, which sends/gets data from mongoDB:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { filter, isEmpty, map } from 'rxjs/operators';
import { Employee } from './employee';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http: HttpClient) { }
uri = 'http://localhost:4000/employees';
data: Employee[];
addEmployee(empObj) {
this.http.post(`${this.uri}`, empObj).subscribe(res => { console.log('Done') })
alert('Done adding data');
}
getEmployees(): Observable<Object> {
return this.http.get(`${this.uri}`)
}
getEmployee(id: number): any {
return this.http.get('https://api.myjson.com/bins/mpg19')
}
deleteEmployee(id: number) {
console.log('ID of employee to be deleted', id);
return this.http.get(`${this.uri}/delete/${id}`);
}
}
And here is my file that performs CRUD operations.
const express = require('express');
const app = express();
const employeeRoute = express.Router();
let Employees = require('../models/Employee');
employeeRoute.route('/').post(function (req, res) {
console.log('coming here', req.body)
let employee = new Employees(req.body);
console.log('data to save is: ', employee)
employee.save().then(employee => { res.status(200).json({ 'employee': 'Employee added in successfully' }) })
})
employeeRoute.route('/').get(function (req, res) {
console.log('Employees Fetched');
let employees = [];
Employees.find({}, function (err, results) {
if (err) {
console.log(err);
}
else {
employees = results;
res.json(employees);
}
})
})
employeeRoute.route('/delete/:id').get(function (req, res) {
console.log('Delete Function Called')
Employees.findByIdAndRemove({ id: req.params.id }), function (err, Employees) {
console.log('Id against which employee is deleted: ', id);
if (err) res.json(err);
else res.json('Successfully removed');
}
});
module.exports = employeeRoute
My application is getting and fetching the data properly, as I was following a written tutorial, I have a little confusion understanding the code. and that is:
how does my addEmployee(empObj) function (in first file) knows which function to call from the CRUD file.
Thanks is advance.
There are a couple of things here you need to become more familiar with.
HTTP Server vs HTTP Client
Your HTTP Client is responsible for sending HTTP(s) requests to a server. When it dispatches this request, it usually has no way of knowing whether the server a) will accept the request or, b) what the server will do with the request. Therefore, when you call http.post(), your HTTP Client does not know what your server will do with that request. You can think of this as a message that your http client sends to a server with some instructions and details.
Your HTTP Server is responsible for accepting, validating and routing incoming requests as well as responding to those requests. Your server alone knows how it wants to handle incoming requests, and whether it will accept or deny them (or at least this is how it should be.)
You can learn more about how HTTP works here:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview
REST
Just as your HTTP Client doesn't have any way of knowing what your server will do with a particular HTTP Request, so to are you (the developer) unable to easily reason with your HTTP Server and all of it's potentially valuable data. It is for this reason that REST aims to establish a set of principles designed around structuring content (or resources) on the internet, with reasonable ways of describing these resources and easy ways address these resources with common English Verbs (GET, PUT, DELETE, POST, etc... ).
You can read more about REST (and find related links) here: https://developer.mozilla.org/en-US/docs/Glossary/REST
Express Routing
Express acts as a software routing layer on your HTTP server. It's important to understand that this means express is responsible for creating and registering virtual routes on your server that point to resources. For example, when you do: http.post('http://myserver.com/api/employees'); you are actually asking express (in this example) to try and find a registered route with the route path of 'api/employees' and respond with a resource (or resources) where appropriate.
Therefore, to answer your question directly - Angular's HTTP Client does not know which function will be run on your Express HTTP Server. Your Express Server uses the route http://localhost:4000/employees to determine what kind of resource you're asking for. In this case, it looks for a route registered to 'employees' and validates whether that route accepts the POSTs. If it does accept POSTs, then it executes the callback associated with that registered route.