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.
Related
I have the following middleware to log all http requests:
#Injectable()
export class RequestMiddleware implements NestMiddleware {
constructor(
#InjectRepository(Request) private requestsRepository: Repository<Request>,
) {}
private readonly logger = new Logger('HTTP');
use(request: Request, response: Response, next: NextFunction) {
response.on('finish', () => {
const { method, originalUrl } = request;
const { statusCode, statusMessage } = response;
const message = `${method} ${originalUrl} ${statusCode} ${statusMessage}`;
return this.logger.log(message);
});
next();
}
}
My goal is to log all requests to a database. I am using TypeORM so I would like to inject the Request repository and log each request that way. When I do this I receive the following error:
Error: Nest can't resolve dependencies of the class RequestMiddleware
The issue is that this middleware isn't part of a module, it's just a single typescript file so how do I import the TypeORM repo module into a normal typescript file so I can use it?
In the module where RequestMiddleware is defined and used, TypeormModule.forFeature([Request]) needs to be added to the imports array
im trying to add graphql to nestjs app. I use postgraphile for the graphql server.
everything works, but i don't know how to protect the '/graphql' endpoint.
the login is done via session, through the nestjs app.
on the needed endpoints (in the existed nestjs app) there is a Session guard which works fine.
i want to add the same behavior to the graphql endpoint.
this is the graphql code:
export const graphqlBootstrap = async (app: INestApplication) => {
app.use(cors());
// what i want to achieve
app.use('/graphql', (req, res, next) => {
if (!req.session.user) res.send('Authetication Error`);
next();
})
app.use(
postgraphile(...)
and the nestjs code the init it:
const bootstrap = async () => {
const { app, start } = await createApplication(TearupsApplicationModule);
app.useGlobalInterceptors(new ApiResponseInterceptor());
configureHiddenFileUploadEndpoint(app.getHttpAdapter());
await Promise.all(app.get(INITIATION_SEQUENCE).map((fn) => fn()));
await graphqlBootstrap(app); // <--
await start();
app.get(EventsEmitter).init(app);
};
is it possible to use the session in a middleware?
I don't know how to reach the ExecutionContext that exist in the Guard.
I read in the docs that there is also ArgumentsHost which might hold the session, but i also don't know how to get it in simple middleware(app.use(...))
Edit 1
i tried to add a middleware to the app module, with forRoutes call.
but the middleware isn't called with the graphql route /graphql.
also, when using route: / - there is no session on the request.
middleware:
export function Logger(req: any, res: Response, next: NextFunction) {
if (req.session) console.log(`Request...`, req.session.user);
next();
}
App Module:
export class MyApp implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(Logger).forRoutes('/');
}
Edit 2
tried this now, still didn't work:
export class MyApp implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(logger)
.forRoutes({ path: 'api/tearups/graphql', method: RequestMethod.ALL });
}
}
Write a functional middleware as follow signature:
import { Request, Response, NextFunction } from 'express';
export function Logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`, req.session.user);
next();
};
Or, a class-based middleware as:
#Injectable()
export class Logger implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('Request...', req.session.user);
next();
}
}
Now apply the middleware as follows:
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(Logger)
.forRoutes(CatsController);
}
}
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?
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)
I had a controller that didn't send the response back.
#Controller('/foo')
export class FooController {
#Post()
public async getBar(#Body() body: DTO, #Res() res) {
const response = await this.doSomething(body);
return response;
}
}
I had to use the res.send method:
#Controller('/foo')
export class FooController {
#Post()
public async getBar(#Body() body: DTO, #Res() res) {
const response = await this.doSomething(body);
res.send(response);
}
}
The cause was the #Res() res parameter. If you delete it, the response will be sent correctly:
#Controller('/foo')
export class FooController {
#Post()
public async getBar(#Body() body: DTO) {
const response = await this.doSomething(body);
return response;
}
}
You have to use #Res({ passthrough: true }) if you want the response to be send using the Nest way.
If you want to send the response like on Express framework use #Res() and add code res.status(200).send()
https://docs.nestjs.com/controllers
WARNING Nest detects when the handler is using either #Res() or
#Next(), indicating you have chosen the library-specific option. If
both approaches are used at the same time, the Standard approach is
automatically disabled for this single route and will no longer work
as expected. To use both approaches at the same time (for example, by
injecting the response object to only set cookies/headers but still
leave the rest to the framework), you must set the passthrough option
to true in the #Res({ passthrough: true }) decorator.