Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I organise throwing business-logic exceptions in NestJs services?

I have a few doubts regarding this github issue discussion: https://github.com/nestjs/nest/issues/310

I want to throw business-domain exceptions from my service's methods as shekohex suggested. I wonder where exactly should I keep them? I believe many of them will be quite similiar across domains (like EntityNotFoundException, ActionForbiddenException etc.) so it would make sense to keep them on the application level (some kind of shared module). On the other hand that makes particular domain less independent (for example, what if I need to extract few of them into another application some time in the future?). What is more, some of the exceptions can be domain-specific and I'd have to keep them inside appropriate domain module's structure.

Let's assume I do keep some of them in the shared module and the rest in the individual domain catalogues. How do I map them to the proper HttpExceptions? If I make global exception filter I also make my domain controllers even more dependent on the application-layer. How do I map domain-specific exceptions? Do I create another module-level exception filter?

Is creating global- or module-level filters a way to go? Decorating every endpoint with UseFilters seems pretty cumbersome.

Thanks in advance for your input!

like image 248
Nivelis Avatar asked Aug 20 '19 07:08

Nivelis


People also ask

Is it possible to create an array of errors in NestJS?

Yes, definitely you can improve it to include all functions in a single try-catch. The only issue is it will return the first error and need to find another approach to returning an array of all errors.

What is RCP exception?

Exception reporting and professionalism The RCP is clear that it believes that unsafe working hours and poor training environments are unacceptable and that exception reporting is a means to highlight such instances so that action can be taken.

How does NestJS work?

NestJS is a backend framework for creating scalable applications. It works as an abstraction over the underlying HTTP server library. Currently, it supports two libraries — Express and Fastify — while still enabling developers to use their own APIs when needed.


1 Answers

This is a nice question I've been dealing with lately.

For some project we wanted to have business errors shared across different domains of our application. What we ended up with was to simply have a shared (or common) folder or module exposing the different domain specific errors we have in our app.

On top on that we use the common exceptions from Nest which are just fine for this job !

Finally, we customize common Nest exceptions with our shared domain specific business errors.

Minimalist user.service.ts reproduction example:

import { NotFoundException, Logger } from '@nestjs/common';
import { UserBusinessErrors } from '../../shared/errors/user/user.business-errors';

// some code ...

// Find the users from the usersIds array
const dbUsers = await this.userRepository.find({ id: In(usersIds) });
// If none was found, raise an error
if (!dbUsers) {
    Logger.error(`Could not find user with IDs: ${usersIds}`, '', 'UserService', true);
    throw new NotFoundException(UserBusinessErrors.NotFound);
}

with its corresponding user.business-errors.ts file within a shared or common module/folder (e.g src/shared/errors/user/user.business-errors.ts):

export const UserBusinessErrors = {
    // ... other errors

    NotFound: {
        apiErrorCode: 'E_0002_0002',
        errorMessage: 'User not found',
        reason: `Provided user ids doesn't exist in DB`
    },

    // ... other errors
}

Or sometimes if you want something more generic, you could use shared errors too:

import { InternalServerErrorException, Logger } from '@nestjs/common';
import { SharedBusinessErrors } from '../../shared/errors/shared.business-errors';

// some code ...

try {
    // ...
} catch (error) {
    Logger.log(SharedBusinessErrors.DbSaveError, 'UserService');
    throw new InternalServerErrorException(SharedBusinessErrors.DbSaveError, error.stack);
}

with corresponding shared.business-errors.ts file:

export const SharedBusinessErrors = {
    DbSaveError: {
        apiErrorCode: 'E_0001_0001',
        errorMessage: `Error when trying to save resource into the DB`,
        reason: 'Internal server error'
    },

    // ... other errors
}

Last thoughts to comment on your different questions:

  • As you stated

Is creating global- or module-level filters a way to go? Decorating every endpoint with UseFilters seems pretty cumbersome.

  • We could take advantage of Nest filters customization and use decorators at the controller or route level too. But to me, you would actually still need to specify what business errors it is related to, so that you add value to you business error and you'd need to raise the error in your service anyway. So having just some shared files containing the domain specific (or business) errors and the more generic ones is fine imo (but maybe not the cleanest / most elegant solution tho).

  • You could put your business errors files within each module's folder and add a webpack config to gather and concatenate all of them into a unique file.

Finally I guess you could even create a npm package if you need those domain specific or shared errors across multiple projects.

Let me know if I'm unclear on anything or/and if it gives you a bit more insight !

like image 112
A. Maitre Avatar answered Sep 18 '22 12:09

A. Maitre