NestJS : Interceptor both map and catchError
Asked Answered
S

2

7

I need a NestJS interceptor that archives requests, both in exceptional and happy-path cases. Created as follows:

public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {

    if (!this.reflector.get<boolean>(RequestMetaData.IS_PUBLIC_ROUTE, context.getHandler())) {
        return next.handle().pipe(
          map(data => {
              const host = context.switchToHttp();
              const req = host.getRequest();
              const resp = host.getResponse();
              this.persistRequest(data, req, resp)
                .then(() => this.logger.log(`Request logged`))
                .catch(e => {
                    this.logger.error(`Error logging request: ${e.message}`);
                });
              return data;
          }));
    }
    return next.handle();
}

Problem:

This only logs the happy path. Because I'm not familiar with RxJS I created another to persist errors. Eg:

public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next
      .handle()
      .pipe(
        catchError(err => {
            return throwError(err);
        })
      );
}

How can I define a single interceptor that archives both paths?

Soapberry answered 8/7, 2019 at 4:46 Comment(0)
H
7

I think you’re using the wrong operator here. The inner map returns a publisher as well.

You should transform the outer publisher with a flatMap so that you create one flow, not a nested flow.

Here is an example in pure RxJS 6:

import { of, EMPTY } from 'rxjs';
import { map, flatMap, catchError } from 'rxjs/operators';

of(3,2,1,0,1,2,3).pipe(
  flatMap(v => {
    return of(v).pipe(
      map(x => {    
        if(x===0) throw Error();
        return 6 / x;
      }), 
      catchError(error => {
        console.log("Shit happens")
        return EMPTY
      }
    )
    )
  } 
))
.subscribe(val => console.log("Request " + val + " logged "));

Each request (here numbers) is flat map into a thing that persists stuff. Flat map means, that the persister returns an observable again. See https://rxjs-dev.firebaseapp.com/api/operators/flatMap

The error handling of those inner observables is done via the catchError operator. It logs the error and than returns an empty observable to indicate that the inner observable is "dead". You could return another observable here and than the inner would continue.

The outer observable, that is your incoming requests, continue each along the way.

I have create a stackblitz app here:

https://rxjs-qkqkm2.stackblitz.io

Good luck with the NestJS and all the different versions of RxJS. This above is version 6.

Edit:

RxJS tap method is a good approach for handling side effects. Implement the intercept method as follows:

public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {

    if (!this.reflector.get<boolean>(RequestMetaData.IS_PUBLIC_ROUTE, context.getHandler())) {

        const host = context.switchToHttp();
        const req = host.getRequest();
        const resp = host.getResponse();

        return next.handle().pipe(
          tap({
              next: (val) => {
                  this.persistRequest(val, req, resp);
              },
              error: (error) => {
                  this.persistRequest(AppError.from(error), req, resp);
              }
          })
        );
    }
    return next.handle();
}
Heddi answered 8/7, 2019 at 5:13 Comment(3)
Hey Michael! How's things? Would you care to provide an example? I'm not following.Soapberry
Things are good. I have updated my answer to clarify the concept. Hope that helps.Heddi
I ended up using tap - thanks for pointing me in the right direction. Incidentally, the last FRP framework I used was one for Objective-C - I think it was called Reactive Cocoa.Soapberry
A
0
public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {

    if (!this.reflector.get<boolean>(RequestMetaData.IS_PUBLIC_ROUTE, context.getHandler())) {
        return next.handle().pipe(
          finalize(() => {
              const host = context.switchToHttp();
              const req = host.getRequest();
              const resp = host.getResponse();
              this.persistRequest(data, req, resp)
                .then(() => this.logger.log(`Request logged`))
                .catch(e => {
                    this.logger.error(`Error logging request: ${e.message}`);
                });
          }));
    }
    return next.handle();
}
Andress answered 26/9, 2024 at 23:11 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Purvis

© 2022 - 2025 — McMap. All rights reserved.