how to modify Request and Response coming from PUT using interceptor in NestJs - nestjs

I am using NestJs. I am using intercepter in my controller for PUT request.
I want to change the request body before the PUT request and I want to change response body that returns by PUT request. How to achieve that?
Using in PUT
#UseInterceptors(UpdateFlowInterceptor)
#Put('flows')
public updateFlow(#Body() flow: Flow): Observable<Flow> {
return this.apiFactory.getApiService().updateFlow(flow).pipe(catchError(error =>
of(new HttpException(error.message, 404))));
}
Interceptor
#Injectable()
export class UpdateFlowInterceptor implements NestInterceptor {
public intercept(_context: ExecutionContext, next: CallHandler): Observable<FlowUI> {
// how to change request also
return next.handle().pipe(
map(flow => {
flow.name = 'changeing response body';
return flow;
}),
);
}
}

I was able to do it by getting request from ExecutionContext
following is the code.
#Injectable()
export class UpdateFlowInterceptor implements NestInterceptor {
public intercept(
_context: ExecutionContext,
next: CallHandler
): Observable<FlowUI> {
// changing request
let request = _context.switchToHttp().getRequest();
if (request.body.name) {
request.body.name = 'modify request';
}
return next.handle().pipe(
map((flow) => {
flow.name = 'changeing response body';
return flow;
})
);
}
}

Related

Exception Interceptor should modify res

I implemented an exception interceptor to intercept my HttpExceptions and return a http response depending on which HttpException is thrown by my application.
In my case I am throwing a 403 http exception in another interceptor which used by a route and declare the exception interceptor as a global one.
My interceptor looks like:
#Injectable()
export class ExceptionInterceptor implements NestInterceptor<Response> {
constructor(
private readonly logger: LoggerService
) {
this.logger.setContext(this.constructor.name);
}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = context.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const url = getOriginalUrl(request);
return next.handle().pipe(
catchError(
exception => {
switch (exception.status) {
case HttpStatus.FORBIDDEN: {
const forbiddenResBody = getForbiddenResBody(exception.response, url);
response.status(HttpStatus.FORBIDDEN);
response.header('Content-type', 'application/problem+json');
this.logger.error(
'Unauthorized',
exception
)
return of(forbiddenResBody)
}
default: {
const invalidServerResBody = getInternalServerResBody(url);
console.log('it is default')
this.logger.error(
'Unexpected exception',
exception
)
return of(invalidServerResBody);
}
}
})
);
}
}
However, the response is not modified is there anything that I am missing ?
Any help would be really appreciated !

NestJS TransformResponse Interceptor

I Have created Interceptor and Decorator for TransformingResponses in NestJS.
Interceptors looks like this:
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { map, Observable } from 'rxjs';
#Injectable()
export class TransformInterceptor implements NestInterceptor {
constructor(private reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const metadata = this.reflector.get<string[]>('TRANSFORM_RESPONSE', context.getHandler());
if (metadata) {
return next.handle().pipe(map((data) => ({ result: data })));
}
return next.handle();
}
}
Issue is that when the service returns null ( entity do not exists in the db or whatever ) this transform response is trying to map the response from the service to some particular DTO. And then in this DTO we are destructing or what ever but when is null you know what is happening...
My question is how to catch this null which is coming from the service in the interceptor before passing it to the DTO and prevent going that route if there is no response from the service

NesJS : using an interceptor for HTTP and WS

I created an interceptor to edit data after passing the controller.
It works with HTTP but not with WS.
This is the code of my interceptor :
#Injectable()
export class SignFileInterceptor implements NestInterceptor {
constructor(private fileService: FilesService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(async (data) => {
const paths = getFilesDtoPaths(data);
for (const path of paths) {
const file = get(data, path);
// Returns a promise
const signed = await this.fileService.signFile(file);
set(data, path, signed);
}
return data; // The data is edited and we can return it.
}),
);
}
}
To use it for HTTP, I add the interceptor to the app module :
providers: [
AppService,
{
provide: APP_INTERCEPTOR,
useClass: SignFileInterceptor,
}
]
With this, all my HTTP requests are intercepted, and the response is correct.
I want to make the same thing with WS using the same interceptor.
#WebSocketGateway({
cors,
allowEIO3: true,
})
#UseInterceptors(SignFileInterceptor) // Interceptor added HERE
#Injectable()
export class EventsGateway {
constructor() {}
#WebSocketServer()
server!: Server;
#SubscribeMessage('name1')
async handleJoinRoom(
): Promise<string> {
return 'john doe'
}
#SubscribeMessage('name2')
async handleJoinRoom(
): Promise<string> {
return 'john doe 2'
}
}
When a WS is triggered, the code is executed, but the data is returned BEFORE the end of my interceptor execution.
The data is not edited.
I appreciate your help.
Change map to mergeMap or switchMap to handle the async execution of the code. map from RxJS is a synchronous method.
This interceptor works well for HTTP and WS.
Another issue in my project caused the problem.
Sorry for the inconvenience.

How to create common class for third-party API requests in NestJS

I am creating NestJS application where I am making third-party API requests. For that I have to write the same thing inside every function in order to get the data.
To make things non-repeating, how can I write on common class that has API request based on GET or POST request and send the response so that I can use that class in every function.
Below is my code:
subscribe.service.ts
#Injectable()
export class SubscribeService {
constructor(#InjectModel('Subscribe') private readonly model:Model<Subscribe>,
#Inject(CACHE_MANAGER) private cacheManager:Cache,
private httpService: HttpService){}
async addSubscriber(subscriberDto:SubscribeDto){
const url = 'https://track.cxipl.com/api/v2/phone-tracking/subscribe';
const headersRequest = {
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
};
try{
const resp = await this.httpService.post(url,subscriberDto,{ headers: headersRequest }).pipe(
map((response) => {
if(response.data.success == true){
const data = new this.model(subscriberDto);
// return data.save();
const saved = data.save();
if(saved){
const msgSuccess = {
"success":response.data.success,
"status":response.data.data.status
}
return msgSuccess;
}
}
else{
const msgFail = {"success":response.data.success}
return msgFail;
}
}),
);
return resp;
}
catch(err){
return err;
}
}
async getLocation(phoneNumber:PhoneNumber){
try{
const location = await this.cacheManager.get<Coordinates>(phoneNumber.phoneNumber);
if(location){
return location;
}
else{
const resp = await axios.post('https://track.cxipl.com/api/v2/phone-tracking/location',phoneNumber,{headers:{
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
}});
const msg:Coordinates = {
"location":resp.data.data.location,
"timestamp":resp.data.data.timestamp
}
await this.cacheManager.set<Coordinates>(phoneNumber.phoneNumber,msg, { ttl: 3600 });
return msg;
}
}
catch(err){
console.log(err);
return err;
}
}
}
As in above code in both function addSubscriber() and getLocation() I need to hit the API repeatedly and add request headers again and again is there any way so that I can create one separate class for request and response and utilize in my service.
How can I achieve desired the result?
To create a common class for making third-party API requests in NestJS, you can follow these steps:
Create a new file in your NestJS project to store the common class.
For example, you could create a file called api.service.ts in the
src/common directory.
In the file, create a new class called ApiService that will be responsible for making the API requests. This class should have a
constructor that injects the necessary dependencies, such as the
HttpService provided by NestJS.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
}
Add methods to the ApiService class for each type of API request you want to make. For example, you might have a get() method for making GET requests, a post() method for making POST requests, and so on. Each method should accept the necessary parameters for making the request (such as the URL and any query parameters or request body), and use the HttpService to make the request.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
async get(url: string, params?: object): Promise<any> {
return this.httpService.get(url, { params }).toPromise();
}
async post(url: string, body: object): Promise<any> {
return this.httpService.post(url, body).toPromise();
}
}
Inject the ApiService wherever you need to make API requests. For example, you might inject it into a service or a controller, and use the methods of the ApiService to make the actual API requests.
import { Injectable } from '#nestjs/common';
import { ApiService } from './api.service';
#Injectable()
export class SomeService {
constructor(private readonly apiService: ApiService) {}
async getData(): Promise<any> {
return this.apiService.get('https://some-api.com/endpoint');
}
}
This is just one way you could create a common class for making third-party API requests in NestJS. You can customize the ApiService class to meet the specific needs of your application

Nestjs Interceptor how to catch http 401 error and resubmit original request

I need to write an http header interceptor to add Authorization header, if there is a 401 error, submit another request for a new token, then resubmit the original request with the new token.
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
return next.handle().pipe(
catchError(async error => {
if (error.response.status === 401) {
const originalRequest = error.config;
var authRes = await this.authenticationService.getAccessToken();
this.authenticationService.accessTokenSubject.next(authRes.access_token);
// I need to resubmit the original request with the new token from here
// but return next.handle(originalRequest) doesn't work
}
return throwError(error);
}),
);
}
But next.handle(originalRequest) doesn't work. How to resubmit the original request in the interceptor? Thank you very much in advance for your help.
I just encountered a similar problem, where I can catch the exception from exception filter but can't do so in interception layer.
So I looked up the manual and found it says:
Any exception thrown by a guard will be handled by the exceptions layer
(global exceptions filter and any exceptions filters that are applied to the current context).
So, if the exception is thrown from AuthGuard context(including the validate method in your AuthService), probably better to move the additional logic by extending the Authguard
like this:
export class CustomizedAuthGuard extends AuthGuard('strategy') {
handleRequest(err, user, info, context, status) {
if (err || !user) {
// your logic here
throw err || new UnauthorizedException();
}
return user;
}
}
or simply using customized exception filter.
It's been a while since the question but maybe it will help someone.
Ok, suppose that we need handle unauthorize exception out of route and guards, maybe service to service. So you can implement a interceptor like that and add some logic to get some data if needed, Ex: inject some Service in the interceptor.
So, throw an unauthorize exception and we are going to intercept it:
#Injectable()
export class UnauthorizedInterceptor implements NestInterceptor {
constructor(
private readonly authService: AuthService,
private readonly httpService: HttpService,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError((err) => {
const {
response: { status, config },
} = err;
// assuming we have a request body
const jsonData = JSON.parse(config.data);
if (status === HttpStatus.UNAUTHORIZED) {
// We can use some data in payload to find user data
// here for example the user email
if (jsonData?.email) {
return
from(this.authService.getByUserEmail(jsonData.email)).pipe(
switchMap((user: User) => {
if (user) {
// Ex: we can have stored token info in user entity.
// call function to refresh access token and update user data
// with new tokens
return from(this.authService.refreshToken(user)).pipe(
switchMap((updatedUser: User) => {
// now updatedUser have the new accessToken
const { accessToken } = updatedUser;
// set the new token to config (original request)
config.headers['Authorization'] = `Bearer ${accessToken}`;
// and use the underlying Axios instance created by #nestjs/axios
// to resubmit the original request
return of(this.httpService.axiosRef(config));
}),
);
}
}),
);
} else {
return throwError(() => new HttpException(err, Number(err.code)));
}
} else {
return throwError(() => new HttpException(err, Number(err.code)));
}
}),
);
}
}

Resources