1

I'm doing a project using NestJS, and while developing the service and controller for a component, I was handling errors using try - catch methods and then throwing them. However, I encountered a certain error that I'm unable to throw, as it results in a blank response body, while other works. For example:

This is a (working) function inside my upload.service.ts:

async saveFile(body, file) {
    try {
      const uploadRepository = getRepository(Upload);
      const result = await uploadRepository.save({
        // assign properties [...]
      });
      return result;
    } catch (error) {
      unlink(file.path);
      console.log(error);  // Output 1 
      throw new BadRequestException(error);  // Response 1
    }
}

So when I purposely make a Bad Request (by not sending a required value in the request body), I get this in the console:
(Output 1)

QueryFailedError: null value in column "type" of relation "upload" violates not-null constraint
    at new QueryFailedError (%PROJECT_DIRECTORY%\node_modules\typeorm\error\QueryFailedError.js:11:28)
    at PostgresQueryRunner.<anonymous> (%PROJECT_DIRECTORY%\node_modules\typeorm\driver\postgres\PostgresQueryRunner.js:247:31)
    at step (%PROJECT_DIRECTORY%\node_modules\typeorm\node_modules\tslib\tslib.js:141:27)
    at Object.throw (%PROJECT_DIRECTORY%\node_modules\typeorm\node_modules\tslib\tslib.js:122:57)
    at rejected (%PROJECT_DIRECTORY%\node_modules\typeorm\node_modules\tslib\tslib.js:113:69)
    at processTicksAndRejections (internal/process/task_queues.js:93:5) {
  length: 393,
  severity: 'ERROR',
  code: '23502',
  detail: 'Failing row contains (51, 15, 2021-03-23 10:52:59.763387, 2021-03-23 10:52:59.763387, example.pdf, application/pdf, uploads\\e0f65f7ab43f1c226b37a9463f3a4745, null).',
  [...]
}

And the proper error returned in the response body, using Postman:
(Response 1)

{
    "message": "null value in column \"type\" of relation \"upload\" violates not-null constraint",
    "length": 393,
    "name": "QueryFailedError",
    "severity": "ERROR",
    "code": "23502",
    "detail": "Failing row contains (51, 15, 2021-03-23 10:52:59.763387, 2021-03-23 10:52:59.763387, example.pdf, application/pdf, uploads\\e0f65f7ab43f1c226b37a9463f3a4745, null).",
    [...]
}

Now, this is another function inside upload.service.ts:

async readFile(res, fileId) {
    try {
      const uploadRepository = getRepository(Upload);
      const found = await uploadRepository.findOne({ id: fileId });

      res.download(found.path, found.originalName);
    } catch (error) {
      console.log(error);  // Output 2
      throw new BadRequestException(error);  // Response 2
    }
  }

When I purposely make a Bad Request (by sending a non-existent ID which uploadRepository.findOne won't find), I get this in the console:
(Output 2)

TypeError: Cannot read property 'path' of undefined
    at UploadsService.readFile (%PROJECT_DIRECTORY%\dist\src\components\uploads\uploads.service.js:52:32)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async %PROJECT_DIRECTORY%\node_modules\@nestjs\core\router\router-execution-context.js:46:28
    at async %PROJECT_DIRECTORY%\node_modules\@nestjs\core\router\router-proxy.js:9:17

But a blank object in the response body, though it correctly has the 400 Status code, using Postman:
(Response 2)

{}

I can tell that the former error, as seen in the console.log output, has an object with more properties. However, I can't tell why I'm unable to return the latter error properly. I have also tried:

throw new NotFoundException({ message: error });
throw new NotFoundException(JSON.stringify(error));
throw new NotFoundException(error.TypeError);

With no luck, as they show an empty object in the message key.

So, the question is: How are those errors different, and what's the correct way to send the latter?

oScarDiAnno
  • 184
  • 1
  • 1
  • 10

2 Answers2

2

Interesting question which made me look deep in the NestJS source. So, here's what's happening.

After throwing the BadRequestException that was passed a TypeError (note that this comes from NodeJS, not NestJS) the BaseExceptionFilter catches the error and passes it down to the ExpressAdapter (assuming you're using express). There, on the following line, they do:

return isObject(body) ? response.json(body) : response.send(String(body));

The body variable refers to the TypeError itself and is an object, so response.json(body) is executed. The thing is, that inside the response.json function, the object which was passed to it, gets stringifed using JSON.stringify. Since the TypeError has no enumerable properties, you get an empty object string {} as a result.

If you're wondering why that is, take a look at this SO-post.

So to get around this, you could create your own error object instead of the TypeError and pass that to BadRequestException.

eol
  • 23,236
  • 5
  • 46
  • 64
  • 1
    The SO-post on the _why_ was very interesting and helpful! Thank you, it allowed me to fix my problem :) May I ask, what whas your procedure to get to the relevant code in NestJS's source and eventually find the problem? – oScarDiAnno Mar 23 '21 at 19:12
  • 2
    Happy to help! Usually I debug into the library's source code using my IDE to see what's really going on. So in your case I created a minimal NestJS project with a controller where I deliberately forced a `TypeError`, set a breakpoint on the throw statement and used "step into" which after a few calls lead me to the `BaseExceptionFilter`. – eol Mar 23 '21 at 20:19
1

After receiving eol's input from his answer, I wondered "How could I see what the structure of an object is"? and so I came into the Object.getOwnPropertyNames() method which "returns an array of all properties (including non-enumerable properties except for those which use Symbol) found directly in a given object." After adding console.log(Object.getOwnPropertyNames(error)); the output was [ 'stack', 'message' ].

Since the message property is a string, what finally worked was:

throw new NotFoundException(error.message);
oScarDiAnno
  • 184
  • 1
  • 1
  • 10