7
votes

I want to create an application with NestJs and TypeORM using MongoDB. Let's assume I have an entity with two unique fields besides the ID field

@Entity()
export class Module extends BaseEntity {
  @ObjectIdColumn()
  public id: ObjectID;

  @Column({ unique: true })
  public moduleName: string;

  @Column({ unique: true })
  public displayName: string;
}

When I want to create a new module with a moduleName or displayName that already exists I'm getting the following exception error

[Nest] 6624   - 2020-02-20 16:31:36   [ExceptionsHandler] E11000 duplicate key error collection: test-mongo.module index: UQ_61128bd419e3c3a6d8d7d565ed9 dup key: { moduleName: "firstModule" } +23849ms
BulkWriteError: E11000 duplicate key error collection: test-mongo.module index: UQ_61128bd419e3c3a6d8d7d565ed9 dup key: { moduleName: "firstModule" }
    at OrderedBulkOperation.handleWriteError (C:\Users\mhermsen\Gitlab Repositories\server\node_modules\mongodb\lib\bulk\common.js:1210:11)
    at resultHandler (C:\Users\mhermsen\Gitlab Repositories\server\node_modules\mongodb\lib\bulk\common.js:519:23)
    at C:\Users\mhermsen\Gitlab Repositories\server\node_modules\mongodb\lib\core\connection\pool.js:404:18
    at processTicksAndRejections (internal/process/task_queues.js:76:11)

So in my catch statement I have to handle the incoming errors. What I have to do is

try {
  // create module
} catch (error) {
  if (/* error is TypeORM error */) {
    if (/* wrong attribute is the module name */) {
      // module name exists ...
    }

    if (/* wrong attribute is the display name */) {
      // display name exists ...
    }
  }
}

Does TypeORM expose premade exceptions? Is there an enum I could use? What would be a good approach to fetch those TypeORM database errors?


It seems some errors are listed here

https://github.com/typeorm/typeorm/tree/master/src/error

but I can't import them and I didn't find anything about the BulkWriteError


In my catch statement it's possible to do something like

const code: number = error.code;

if (code === 11000) {
  throw new ConstraintException(error.errmsg);
}

throw error;

but as you know this isn't the best approach. Also this error object doesn't tell me if the moduleName or displayName is invalid. Just within a string

errmsg: 'E11000 duplicate key error collection: test-mongo.module index: UQ_61128bd419e3c3a6d8d7d565ed9 dup key: { moduleName: "firstModule" }',

Do I have to create my own mapping from those error codes to exceptions?

1
You can't find anything about the error in typeorm lib because it's thrown by mongo adapter. Here you can find the error you mentioned. Also the best you can do is to use @types/mongodb which exposes MongoError:: code?: number. But unfortunately the package doesn't provide enum with list of errors.mrkosima
Thanks for your reply. So the error codes are not related to TypeORM right? So the only solution for this would be to make multiple checks via select before inserting data?hrp8sfH4xQ4
Yes, such errors are db-related. As an option you can validate that the record with the same unique fields doesn't exist. But I'd rely on mongo functionality with error handling - like your code example with ConstraintException. Also as you can see there's a room for a contribution to @types/mongodb - with enum of error codes :)mrkosima
the problem is that I have other projects relying on Postgres/MSSQL and a nice way of error handling would be awesome :)hrp8sfH4xQ4
NestJS provides DI mechanism. You can implement db-specific error handler with generic errors, and inject the handler to the service the one you need (e.g depending on your app config).mrkosima

1 Answers

0
votes

I am unsure if you found an answer to this, but here is an alternate approach that I use. This works regardless of the typeorm database type.

I try to use custom decorators to catch the exceptions when the database service is called (any crud operations like this)

So here is my custom decorator (function decorator. You can choose the type of decorator. It can be a class decorator as well)

target: - current object's prototype

property: - name of the method

descriptor: - property descriptor of the method i.e Object.getOwnProperDescriptor

NOTE: Nestjs has the current implementation with the way swagger is implemented - where if the args/return values of the method is changed, it doesn't render the Swagger API sections with the proper response objects. Hence used the concept of Proxy to change the behavior

export const DatabaseFailureHandler = (target: any, property: string, descriptor: PropertyDescriptor): any => {
  const className = target.constructor.name;
 // 
  const original = descriptor.value;

  let customException;
  // const proxyHandlerFn = function(target, )
  descriptor.value = new Proxy(original, {
    apply: async function(target, thisArg, args) {
      console.log(`Call with args: ${args} -------${className}#`);
      let result;
      try {
        result = await target.apply(thisArg, args);
        console.log('result in databasefailure handling"', result);
        //WARNING!!! - Test only
        // throw new Error('DatabaseException');
        return result;
      } catch (err) {
        // TODO: Replace with the actual logger with the logs of service and class names
        console.log('err capt:', err);

// Below is my custom exception implementation that has my required details. You can 
 //modify it to have the className, methodName args, etc...
        customException = new AppConfigService(new AppUtilService()).retrieveCustomHttpException(
          HttpStatus.INTERNAL_SERVER_ERROR,
          MATRIX_API.ERROR_MSG.DATABASE_ERROR + `:${err.name}`,
          err,
          HttpStatus.INTERNAL_SERVER_ERROR.toString(),
          MATRIX_API.DATABASE.ERR_TYPE.DATABASE_EXCEPTION_ERR_TYPE,
          err.message
        );
        throw customException;
      }
    }
  });
};

Now this is implemented as a custom decorator like this in my service.ts

@DatabaseFailureHandler
  async updateXXXXSkill(
    corpId: string,
    skillId: number,
    skillUpdate: UpdateRequestModel
  ): Promise<UpdateResponseModel> {
// Either get the repo from connection or entity manager or Inject them if TypeORMModule is used
    const entity: Employee = this.repository.create({
      ...skillUpdate,
      corpId,
      skillId
    });
    await this.repository.update(
      {
        corpId,
        skillId
      },
      entity
    );
    return XXXXX;
  }

Hope this approach enables you to capture all the error details and throw them via custom exception filter. this is the centralized filter that I've written to capture any exceptions. It is registered at the start of the server. You can follow the official nestjs docs