How to use the AWS X-Ray with Nest.js? - nestjs

AWS X-Ray is support Express and Restify middleware but not support Nest.js.
Nest.js can't open segment and close segment to AWSXRay because it routes with typescript decoration.
How to use the AWS X-Ray with the Nest.js

Hmm, this is one of those situations that could be very interesting and difficult to work with. You can of course set up the openSegement call in the standard Nest middleware (looks just like Express middleware), but the closeSegment is a bit more difficult. I think (and I'm taking a long shot here as I have no real way to test this) you can create an interceptor and inject the HttpAdapter into it, check in incoming route before the request is made and see if it is a route you want to cover with X-Ray, if so mark a boolean and in the observable response (next.handle()) you can get the HttpAdapter instance and call the closeSegment function. In other words (and this will be really rough code):
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '#nestjs/common';
import { HttpAdapterHost } from '#nesjts/core';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import * as xRay from 'aws-xray-sdk-express';
#Injectable
export class XRayInterceptor implements NestInterceptor {
constructor(private readonly httpAdapter: HttpAdapterHost) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
let coverRoute = false;
const req = context.switchToHttp().getRequest();
if (req.path === 'yourPath') {
coverRoute = true;
}
return next.handle()
.pipe(
tap(() => (coverRoute && this.httpAdapter.use(xRay.xrayExpress.closeSegment()))
);
}
You might also probably be able to set up the openSegment in the interceptor as well, but again, all of this is untested and may not work as expected. I'm jsut trying to think of a way to maybe make this possible. Without access to error handling middleware your options would be to look at interceptors and filters, and it seems the closeSegement is to be an error handler like filters would be, so I'm not sure how you would catch errors otherwise. Maybe a filter is the best route, you may just have to play with ideas from here. Hopefully someone can give a bit more insight.

Related

FST_ERR_REP_ALREADY_SENT when trying to sendFile on Nestjs notfoundExecption

I want to serve spa app with nestjs and fastify, it works but when I refresh a 404 is triggered. So I wrote an exception to catch the 404 and send the file but I keep getting FST_ERR_REP_ALREADY_SENT error.
My code below
import { Catch, ExceptionFilter, ArgumentsHost, HttpException, NotFoundException } from '#nestjs/common';
import { FastifyReply } from 'fastify';
#Catch(NotFoundException)
export class NotFoundExceptionFilter implements ExceptionFilter {
catch(_exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse() as FastifyReply;
response.sendFile('index.html');
}
}
I want 404 to serve the index.html file
I have had this issue in the past and an update from Nest 8 to 9.x fixed the bug. #Cavdy you may want to update to the latest Nest, atleast anything >=9 and you should be good to go.
It's probably catching something on notFoundHandler before it reaches your exception filters. I recommend setting it on notFoundHandler instead of using exception filters reference

Is there a way to get request context within a decorator in Nest JS

I am trying to build a decorator to "log" request info
export const Tracking = () => {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const method = descriptor.value;
descriptor.value = async function(...args: any[]) {
console.log(/** Request info */)
console.log(/** Headers, Body, Method, URL...*/)
return method.call(this, ...args);
}
}
}
and try to use it on a controller method like this.
export class Controller {
#Get('/path')
#Tracking()
public async getData(#Headers('user') user: User) {
return this.service.getData(user.id);
}
}
If this is impossible, is there a way to apply interceptor to some method of controller?
Or is there a thread(like)-level context for request?
Thanks!!
Decorators don't have access to the request information because of what a decorator is. It's a higher order function that is called to set metadata for the class, class member, class method, or class method parameter. This metadata can be read at runtime, but it is called and set essentially as soon the file is imported. Due to this, there's no way to call a decorator on each request, even Nest's #Body() and #Req() are called at the time of import and read at the time of the request (actually earlier but that's besides the point).
What you're looking for here sounds more like an interceptor, like Micael Levi and hoangdv have already mentioned. The Nest docs show a basic logging example, and there are packages out there like #ogma/nestjs-module (disclaimer: I'm the author) that handle this request logging/tracking for you including the addition of correlation IDs.

How to make database request in Nest.js decorator?

I want to find a table row by request parameter. I know how to do it in service but I'm trying
to do it also in decorator.
My decorator:
import { BadRequestException, createParamDecorator, ExecutionContext } from '#nestjs/common';
export const GetEvent = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const { eventId } = request.params;
// Something like in service:
// const event = await this.eventModel.findByPk(eventId);
// return event;
});
I know that it's impossible to inject service in decorator but maybe some hacks to make database requests before calling service methods?
Theoretically, you could use a pacakge directly (like if you use TypeORM you could use the typeorm package), but there's a few things to note:
decorators really shouldn't be calling databases, it'll lead to crazy difficult stack traces
decorator methods can't be async, so you'd need callbacks, which decorators don't really work well with
querying the database really should be done in the controller or service. Just a best practice.

nestjs save each request info without hitting database twice

I want to save each request (path, method, and userId) that comes to the server without having to hit the database twice, and also without messing up the main logic in services files with transactions.
Initially, I was trying to use an interceptor because it gets invoked after auth guards "which attaches the user to request" and before request handlers, but I faced two issues.
first, the fact that the interceptor will call the database to save a new record and then forward the request to handlers which will again hit DB again to handle the request. Secondly, It didn't work anyway because of dependancy injection problems.
code below is not working due to dependency errors as I mentioned, but It will give u an idea about what I need to acheive.
import { Injectable,
NestInterceptor,
Inject,
ExecutionContext,
CallHandler,
HttpException,
HttpStatus } from '#nestjs/common';
import { Observable } from 'rxjs';
import { getRepositoryToken } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { HistoryEntity } from '../../modules/history/history.entity';
#Injectable()
export class HistoryInterceptor implements NestInterceptor {
constructor(
#Inject(getRepositoryToken(HistoryEntity))
private readonly historyRepo: Repository<HistoryEntity>
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { user, path, method } = request
if (!user) {
throw new HttpException('something terrible happened!', HttpStatus.BAD_GATEWAY);
}
const history = this.historyRepo.create({
path,
userId: user.id,
});
this.historyRepo.save(history);
return next.handle();
}
}
PS. from a performance point of view, It would also be great to not halt the request execution to save these info in db, in other words, Is it ok to NOT use await in this particular situation? because essecntially this is a system related operation and so, Node [rocess shouldn't wait for this step to process and return response to client.
Thanks in advance.

Access request object within validation pipe

I am trying to access to Request object from within a Validation Pipe in nestjs
In order to verify uniqueness of certain fields, I require the ID/UUID parameters supplied with PUT/PATCH request (not available in the data structure itself)
any idea?
Currently, it is not possible to access the request object at all in a pipe. If you need the request you can use a guard or an interceptor.
If you are working on verifying uniqueness, that sounds like a part of business logic more than just about anything else, so I would put it in a service and handle the query to the database there. Just my two cents.
Edit 11/17/2020
After learning way more about how the framework works as a whole, technically it is possible to get the entire request object in a pipe. There are two ways to go about it.
Make the pipe #Injectable({ scope: Scope.REQUEST }) so that it is request scoped. This will end up creating a new pipe on each request, but hey, if that's your cup of tea then great.
Make a custom parameter decorator as custom decorators get completely passed into pipes as they are. Do note, that this could impact how the ValidationPipe is functioning if that is bound globally, at the class, or method level.
We can create a Pipe and access request object. We can move further and update the Body as well, if needed.
Following is an example scenario, where createdBy field should be added to the Body dynamically. Let's say user details are available from request:
// user.pipe.ts
import { Request } from 'express'
import { REQUEST } from '#nestjs/core'
import { Injectable, Inject, Scope, PipeTransform } from '#nestjs/common'
#Injectable({ scope: Scope.REQUEST })
export class UserPipe implements PipeTransform<any> {
constructor(#Inject(REQUEST) protected readonly request: Request) {}
transform(value) {
let email = this.request["user"].email;
value['createdBy'] = email;
return value
}
}
We can now use this in controller like this:
// someentity.controller.ts
#Post()
public async create(
#Body(SetUserPipe) dto: SomeEntityDto,
): Promise<SomeEntity> {
....
}

Resources