I created custom nest js guard which is running before every request and I have added a property called all() on the request in that guard - nestjs

I have created a interface which is extending the request (of express)
I m adding property called all() in it
which contains the body , query and params in it
import { Request as BaseRequest } from 'express';
export interface Request extends BaseRequest {
all(): Record<string, any>;
}
this is the interface
which is extending the express request
and i m adding this all() property using the guard
this is the implementation of it
#Injectable()
export class RequestGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
this.requestHelper(context.switchToHttp().getRequest());
return true;
}
requestHelper(request: any): any {
const all = function (): Record<string, any> {
return {
...request.query,
...request.body,
...request.params,
};
};
request.all = all;
return request;
}
}
in the main.ts file i have used this guard
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '#nestjs/common';
import { RequestGuard } from './core/guards/request.guard';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
app.useGlobalGuards(new RequestGuard());
await app.listen(3000);
}
bootstrap();
and i have tried consoling the all() property in the guard and it's working
its mean request is flowing in it
when i try to get this all() property in my controller then it showing
Cannot read properties of undefined (reading 'all')
That's how i m calling it
import {
Controller,
Get,
Post,
Param,
Body,
Req,
Res,
UseGuards,
} from '#nestjs/common';
import { RequestGuard } from 'src/core/guards/request.guard';
import { Request } from 'src/core/http/Request';
import { Response } from 'src/core/http/Response';
#UseGuards(RequestGuard)
#Controller('customers')
export class CustomersController {
constructor(private customersService: CustomersService) {}
#Get('/order-data/:id')
async OrderData(#Param('id') id: string, req: Request, #Res() res: Response) {
console.log(req.all());
const data = await this.customersService.allOrdersData(parseInt(id));
return data;
}
}
I m calling the route localhost:3000/customers/order-data/1
console.log(req.all());
It should print {id:{'1'}}
But it's giving error
Cannot read properties of undefined (reading 'all')

You're missing the #Req() for your req property in the OrderData method.

Related

How to Extract a Value from microservice returned Observable in NestJS?

I am trying to implement NestJS Microservice to Validate my Bearer Token:
Whenever I get TCP response from Microservice which decides whether token is valid or not, it return Observable object, I am unable to extract the response from that Observable, please help me. Not getting proper way to extract the value from Observable in Nestjs, already tried lastValueFrom from RXJS
Here is the Controller to Make a call to Microservice(consumer/caller of microservice):
app.controller.ts
import { Body, Controller, Get, Post } from '#nestjs/common';
import { AppService } from './app.service';
import { PayloadDTO} from './payload.dto';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getHello(): string {
return this.appService.getHello();
}
#Post()
createUserData(#Body() payload: PayloadDTO) {
const token = header.authorization.replace('Bearer ', '')
const isValidToken = this.appService.validateToken(token);
}
if(isValidToken) {
this.appService.CreateRecord(Payload : PayloadDTO)
} else {
//Throw New UnathorizedException
}
}
app.service.ts
import { Inject, Injectable } from '#nestjs/common';
import { ClientProxy } from '#nestjs/microservices';
import { PayloadDTO } from './payload.dto';
#Injectable()
export class AppService {
constructor(
#Inject('COMMUNICATION') private readonly commClient: ClientProxy
) {}
// service method to call and validate token
validateToken(token: String) {
//calling microservice by sending token
const checkToken: Observable = this.commClient.send({cmd:'validate_token'},token);
//Unable to extract value from Observable here, it is returning observable object
return extractedResponse;
}
// service method to create record
createRecord(payload: PayloadDTO) {
//code related to creating new Record
}
}
app.controller.ts - Microservice Project
import { Controller, Get } from '#nestjs/common';
import { MessagePattern } from '#nestjs/microservices';
import { AppService } from './app.service';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#MessagePattern({ cmd: 'validate_token' })
validateToken() {
return this.appService.checkToken();
}
}
Finally I got the answer on my own:
In Microservice Consumer/Caller app.controller.ts I forgot to use await before calling this.appService.validateToke(token)
#Post()
async createUserData(#Body() payload: PayloadDTO) {
const token = header.authorization.replace('Bearer ', '')
const isValidToken = await this.appService.validateToken(token);
}
To convert/extract value from Observable:
In app.service.ts (consumer/caller of microservice) :
use async before validateToken
use await lastValueFrom(checkToken)
return resp
// service method to call and validate token
async validateToken(token: String) {
//calling microservice by sending token
const checkToken = this.commClient.send({cmd:'validate_token'},token);
const resp = await lastValueFrom(checkToken);
reutn resp;
}

How to use mongoose.isValidObjectId as a middleware in nestjs?

I have an issue with repetitive requests for checking an Order id, if it is valid ObjectId or not. I got this error:
CastError: Cast to ObjectId failed for value "629b9fbd620dbc419a52e8" (type string) at path "_id" for model "Order"
After a lot of Googling, I found two approaches to tackle the problem, however I'll have to duplicate these codes for each service, which isn't a good idea.
First approach:
if (!mongoose.Types.ObjectId.isValid(req.params.id)) {
throw new HttpException('Not a valid ObjectId!', HttpStatus.NOT_FOUND);
} else {
return id;
}
Second approach:
if (!mongoose.isValidObjectId(req.params.id)) {
throw new BadRequestException('Not a valid ObjectId');
} else {
return id;
}
I used below codes for making and using a middleware, thus I could check ID whenever a service using an id parameter.
validateMongoID.ts
import {
BadRequestException,
Injectable,
NestMiddleware,
} from '#nestjs/common';
import { Request, Response, NextFunction } from 'express';
import mongoose from 'mongoose';
#Injectable()
export class IsValidObjectId implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
// Validate Mongo ID
if (req.params.id) {
if (!mongoose.isValidObjectId(req.params.id)) {
throw new BadRequestException('Not a valid ObjectId');
}
}
next();
}
}
orders.module.ts
export class OrdersModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(IsValidObjectId).forRoutes('/');
}
}
After trying as a middleware in the orders.modules.ts, I got the same error mentioned above. So, any idea to use it as a middleware?
I had to do this exact thing a couple of weeks ago.
Here is my solution. Works perfectly fine. Not a middleware, though.
id-param.decorator.ts
import { ArgumentMetadata, BadRequestException, Param, PipeTransform } from '#nestjs/common';
import { Types } from 'mongoose';
class ValidateMongoIdPipe implements PipeTransform<string> {
transform(value: string, metadata: ArgumentMetadata) {
if (!Types.ObjectId.isValid(value)) {
throw new BadRequestException(`${metadata.data} must be a valid MongoDB ObjectId`);
}
return value;
}
}
export const IdParam = (param = '_id'): ParameterDecorator => (
Param(param, new ValidateMongoIdPipe())
);
Usage
// If param is called _id then the argument is optional
#Get('/:_id')
getObjectById(#IdParam() _id: string) {
return this.objectsService.getById(_id);
}
#Get('/:object_id/some-relation/:nested_id')
getNestedObjectById(
#IdParam('object_id') objectId: string,
#IdParam('nested_id') nestedId: string,
) {
return this.objectsService.getNestedById(objectId, nestedId);
}
How it works
When using the #Param decorator you can give it transform pipes that will validate and mutate incoming value.
#IdParam decorator is just a #Param with the ValidateMongoIdPipe provided as a second argument.
I have found another way to solve it with the help of Lhon (tagged in comments).
create a file (I named it globalErrorHandler.ts) as follows:
import {
ArgumentsHost,
ExceptionFilter,
HttpException,
HttpStatus,
InternalServerErrorException,
} from '#nestjs/common';
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: InternalServerErrorException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
/**
* #description Exception json response
* #param message
*/
const responseMessage = (type, message) => {
response.status(status).json({
statusCode: status,
path: request.url,
errorType: type,
errorMessage: message,
});
};
// Throw an exceptions for either
// MongoError, ValidationError, TypeError, CastError and Error
if (exception.message) {
const newmsg: any = exception;
responseMessage(
'Error',
newmsg.response?.message ? newmsg.response.message : exception.message,
);
} else {
responseMessage(exception.name, exception.message);
}
}
}
add below line to main.ts
app.useGlobalFilters(new AllExceptionsFilter());
create another file (I named it validateMongoID.ts) as follows:
import {
BadRequestException,
Injectable,
NestMiddleware,
} from '#nestjs/common';
import { Request, Response, NextFunction } from 'express';
#Injectable()
export class IsValidObjectId implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
// Validate Mongo ID
if (req.params.id) {
if (!/^[a-fA-F0-9]{24}$/.test(req.params.id)) {
throw new BadRequestException('Not a valid ObjectId');
}
}
next();
}
}
last step: import it as a middleware in app.module.ts
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(IsValidObjectId).forRoutes('*');
}
}

NestJS, GraphQL, class-validator - access request object inside validate method

I have a custom class-validator rule:
import { Injectable } from "#nestjs/common";
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from "class-validator";
#ValidatorConstraint({ name: "Foo", async: true })
#Injectable()
export class FooRule implements ValidatorConstraintInterface {
async validate(value: unknown, args: ValidationArguments) {
// how to access request object from here?
}
defaultMessage(args: ValidationArguments) {
return "NOT OK.";
}
}
How do I access the request object inside the validate() method?
I ended up using a custom interceptor:
import { Injectable, NestInterceptor, CallHandler, ExecutionContext } from "#nestjs/common";
import { GqlExecutionContext } from "#nestjs/graphql";
import { ForbiddenError } from "apollo-server-core";
import { Observable } from "rxjs";
#Injectable()
export class FooInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// get gql execution context from http one
const gqlCtx = GqlExecutionContext.create(context);
// holds data passed in a gql input
const args: unknown[] = gqlCtx.getArgs();
// req object (can be used to obtain jwt payload)
const req = gqlCtx.getContext().req;
// validation logic here
const validationPassed = true;
if (validationPassed) {
// invoke the route handler method
return next.handle();
}
// will be caught by nest exceptions layer
throw new ForbiddenError("Not allowed.");
}
}

How can I return a non empty response using Nest JS api and postman

I am using Nest Js to setup my server. I tried to fetch data from Postman to test if the API urls work. However I find that I get empty response from the server or undefined value from the postman request. The below code is the users.controller.ts
import {
Body,
Controller,
Delete,
Get,
Header,
Param,
Post,
Put,
} from '#nestjs/common';
import { User } from './users.entity';
import { UsersService } from './users.service';
#Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
/* #Get('/api/users')
index(): string {
return 'This will return user.';
} */
#Get()
index(): Promise<User[]> {
return this.usersService.findUsers();
}
#Post('create')
#Header('Content-Type', 'application/json')
async create(#Body() userData: User): Promise<any> {
console.log(userData.UserId, userData.UserType + ' Check if working');
return this.usersService.create(userData);
}
#Put(':userid/update')
async update(#Param('userid') userid, #Body() userData: User): Promise<any> {
userData.UserId = Number(userid);
console.log('Update #' + userData.UserId);
return this.usersService.update(userData);
}
#Delete(':userid/delete')
async delete(#Param('userid') userid): Promise<any> {
return this.usersService.delete(userid);
}
}
and this code is users.service.ts
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './users.entity';
import { UpdateResult, DeleteResult } from 'typeorm';
#Injectable()
export class UsersService {
constructor(
#InjectRepository(User) private userRepository: Repository<User>,
) {}
async findUsers(): Promise<User[]> {
return await this.userRepository.find();
}
async create(user: User): Promise<User> {
console.log(user.UserType + 'User type');
return await this.userRepository.save(user);
}
async update(user: User): Promise<UpdateResult> {
return await this.userRepository.update(user.UserId, user);
}
async delete(userid): Promise<DeleteResult> {
return await this.userRepository.delete(userid);
}
}
If you can see the post request, there is a console.log() to check whether there is a response or not. Hence I am getting an undefined value instead. I am seeking for support to understand where I am going wrong. I am not able to traceback the error as well.
Welcome!
You are not logging what your service returns, instead you are logging what your service receives as the body of the request:
#Post('create')
#Header('Content-Type', 'application/json')
async create(#Body() userData: User): Promise<any> {
// ##################################
// here, you are logging userData, which is the object your
// service receives as input
// ##################################
console.log(userData.UserId, userData.UserType + ' Check if working');
return this.usersService.create(userData);
}
If that console.log line is logging undefined, it means you are not sending the right data in your HTTP Post request body in postman.
You should be sending a User json object as the request payload.
Had the same problem and solved it by annotating my return object.
export class CriarTarefaDto {
public nome: string;
public ativo?: boolean;
}
This was my object that always returned me empty when checking the return of the #Body annotation. I fixed it by including class-validator lib annotations in my DTO properties.
import { IsBoolean, IsString } from 'class-validator';
export class CriarTarefaDto {
#IsString()
public nome: string;
#IsBoolean()
public ativo?: boolean;
}
In your DTO object you need to use some annotation or extend the DTO with classes that register its properties.

How to instrument a controller in nestjs interceptor?

I want to instrument every method of a nestjs controller for APM purposes.
I wrote the following interceptor in order to instrument the controller invocation.
However, I do not know how to properly wrap the call to next.handle().
I do not have any experience using RxJS Observables.
Question: Is it possible to wrap the invocation properly and if so how?
The current approach seems to measure the controller's execution time but does not set a correct tracer scope for the controller's method. I guess the issue is that next.handle() must be wrapped too.
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "#nestjs/common";
import { Reflector } from "#nestjs/core";
import { Observable } from "rxjs";
import { PATH_METADATA } from '#nestjs/common/constants';
import tracer from "dd-trace";
#Injectable()
export class ApmInterceptor implements NestInterceptor {
constructor(private readonly reflector: Reflector) {}
public intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const request: Request = context.switchToHttp().getRequest();
const path = this.reflector.get<string[]>(PATH_METADATA, context.getHandler());
const method = request.method;
const observable = next.handle();
tracer.trace(`[${method}] ${path}`, () => new Promise((resolve, reject) => {
observable.subscribe({
complete: resolve,
});
}));
return observable;
}
}
Faced a similar issue using OpenTelemetry-js, in order to set the correct scope I've to wrap the handle() Observable into an Async promise to set the context, and then wrap the promise again as Observable for the rxjs pipeline (Observable -> Promise -> Observable)
import {from, Observable} from 'rxjs';
...
async intercept(executionContext: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
const request: Request = context.switchToHttp().getRequest();
const path = this.reflector.get<string[]>(PATH_METADATA, context.getHandler());
const method = request.method;
const observable = tracer.trace(`[${method}] ${path}`, () => new Promise((resolve, reject) => {
return next.handle().toPromise();
}));
return observable.pipe(
map(value => {
// Here you can stop your trace manually
return value;
}),
catchError(error => {
// Here you can stop your trace manually
throw error;
}))
}
For OpenTelemetry you have to create/stop the span and set the correct context:
const span = trace.getTracer('default').startSpan(spanName);
const observable = from(context.with(trace.setSpan(context.active(), span), async () => {
return next.handle().toPromise();
}));
return observable.pipe(
map(value => {
span.stop();
return value;
}),
catchError(error => {
span.addEvent('error', {error: error});
span.stop();
throw error;
}))

Resources