NestJS tip: fine-grained exception filtering for the same exception class!
Tags: nestjs, node, javascript, typescript
Published: 2023-06-17 21:08:35

> for NestJS v8, v9 and v10

## What

We can now have multiple exception filters for the same error class like this:

```ts
class MongoDbDuplicatedKeyConstraintErrorToCatch {
  static [Symbol.hasInstance](instance: unknown): boolean {
    // Any condition here:
    return instance instanceof MongoError && instance.code === 11000
  }
}

// This only catches `MongoError` errors that has the error code '11000'
@Catch(MongoDbDuplicatedKeyConstraintErrorToCatch)
export class MongoDbDuplicatedKeyConstraintFilter implements ExceptionFilter {
  catch(exception: MongoError, host: ArgumentsHost) {
    // Your exception handler code here ...
  }
}
```

or using the [`nestjs-conditional-exception-filter`](https://www.npmjs.com/package/nestjs-conditional-exception-filter) package to make it generic:

```ts
import { Catch, ExceptionFilter } from '@nestjs/common'
import { filter } from 'nestjs-conditional-exception-filter'

@Catch(
  filter({
    for: MongoError,
    when: error => error.code === 11000,
  })
)
export class MongoDbDuplicatedKeyConstraintFilter implements ExceptionFilter {
  catch(exception: MongoError, host: ArgumentsHost) {
    // Your exception handler code here ...
  }
}
```

I don't know about you but I'd rather use the above approach than the following one:

![alternative](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jknhb6hjbbbqthgbxp8v.png)

## Why

Let's say you want to handle exceptions raised by some 3rd-party package but such exceptions are somewhat too generic, they use the same error class to represent different errors.

A real world example would be the `QueryFailedError` from `typeorm`, or `MongoError` from `mongodb` package. Both of them are wrappers. The error object may have some discriminated field that holds the underlying platform error code.

We could use [exception filters](https://docs.nestjs.com/exception-filters) from `@nestjs/common` to handle them but such abstraction doesn't allows us to conditionally catch the exception. This missing feature was requested in 2020 here: https://github.com/nestjs/nest/issues/4516 and in 2023 here: https://discord.com/channels/520622812742811698/1085108578257489970/1085108578257489970 (and few other times)

Thus if we want to handle some specific error raised by `mongodb` lib, we would have to catch all objects that are instance of `MongoError` to later conditionally treat it, like this:

```ts
@Catch(MongoError)
export class MongoErrorFilter extends BaseExceptionFilter {
  catch(exception: MongoError, host: ArgumentsHost) {
    if (exception.code === 11000) {
      // Your exception handling code here ...
    } else {
      return super.catch(exception, host)
    }
  }
}
```

Imagine how fast that code would grow when you starting handling others error codes for the same `MongoError` exception. And how confusing it will look like since it violates the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single-responsibility_principle) and the [Open-Closed Principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle).

Of course that you could split the code into several files and use the [Strategy design pattern](https://refactoring.guru/design-patterns/strategy) but you could also follow that other approach I just showed you [here](#what). Keep reading to understand how that works.

## How

Under the hood, NestJS will look up for the exception filter that, for a given exception raised from your app, matches the following condition: `exception instanceof ExceptionMetaType` ([here's](https://github.com/nestjs/nest/blob/da27730cd4ef37fdf7f29aafee53ed8ac2a8b4b8/packages/common/utils/select-exception-filter-metadata.util.ts#LL11C51-L11C68) the code)

Knowing that, we can use the well-known JavaScript symbol [`Symbol.hasInstance`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance) to create a class that will act like a 'attribute-based exception filter' that Nest will invoked later:

```ts
class FooError {
  static [Symbol.hasInstance](instance: any) {
    // Any condition here:
    return typeof instance.bar !== 'undefined'
  }
}
```

to later use that as the error being caught by our exception filter like so:

```ts
import { Catch, ExceptionFilter } from '@nestjs/common'

@Catch(FooError)
export class MyFooErrorFilter implements ExceptionFilter {
  catch(exception, host) {
    // ...
  }
}
```

now, for that `FooError`, any error raised with a field `bar` in it will be caught by our exception filter. If the error does not meet this condition, another filter could catch it, as usual.