Node.js When updating a document failed, is best to throw an exception or returning information? - node.js

I have an updateDocument method in a class for a service layer in a node.js, express, mongoose application. I'm wondering what is the best practice for handing cases where the update method didn't update a document, for example if the wrong id is passed in.
Version 1: If the update wasn't successful, return an object with success: false:
async updateDocument(id, updates) {
const output = await this.DocumentModel.updateOne({ _id: id }, updates);
let message = 'Something went wrong';
let success = false;
let updatedItem = null;
if (output.nModified) {
message = 'Successfully updated document.';
success = true;
updatedItem = await this.getDocument(id);
}
return { message, success, updatedItem};
}
Version 2: If the update wasn't successful, throw an error:
async updateDocument(id, updates) {
const output = await this.DocumentModel.updateOne({ _id: id }, updates);
let updatedItem;
if (output.nModified) {
updatedItem = await this.getDocument(id);
} else{
throw new Error("The document wasn't updated")
}
return updatedItem;
}
Do you always throw an exception when the input, such as a bad id, isn't correct? Or could you return information about the update being a success or not? As newbie node.js developer, I'm not sure I am grasping the full picture enough to recognize problems with either method.

There is no golden way, only principles that lead to robust and well-maintainable software.
Generally, you should use a try-catch-statement for all kinds of errors that are not in your control (connections, disk space, credentials, ...) . The errors should then be handled as soon as possible, but not before. The reason for this is that you often don't know, yet, how to handle an error in an appropriate manner at a lower layer.
For logical "errors" that you can expect (wrong input format, missing username, unknown options, ...), you should use an if-statement or a validation function and then throw an error, if anything is not as expected.
In your case, you should check, if the methods updateOne or getDocument can throw errors. If yes, you should wrap these functions with a try-catch-statement.
A few more tips:
Both versions of your code are good. But I would prefer version 2 because it is more concise.
If you are sure that there is always an output object, you can destruct the nModified property like this:
const { nModified } = await this.DocumentModel.updateOne({ _id: id }, updates);
If you use a negative if-statement, you can reduce the depth of indentation and you can use const variables:
if (!nModified) {
throw new Error("The document wasn't updated")
}
const updatedItem = await this.getDocument(id);
Now, you could also directly return this.getDocument(id) and don't need the variable updatedItem anymore.
You can finally handle your errors in your controller classes.
You can use custom error classes to be consistent in your error handling and error messages all over your app.
I hope this is at least a bit helpful.
References
These are some similar questions with good answers. But you need to take care because many code examples are not in modern JavaScript.
A general discussion about the pros and cons of Error-Handling vs.
if-else-statements is done here:
What is the advantage of using try {} catch {} versus if {} else {}
Error-Handling in Node.js is discussed here in this thread:
Node.js Best Practice Exception Handling

It seemed like there were a lot of different opinions on this and not one go-to method. Here's some information I found and what I ended up doing.
When to throw an exception?
Every function asks a question. If the input it is given makes that question a fallacy, then throw an exception. This line is harder to draw with functions that return void, but the bottom line is: if the function's assumptions about its inputs are violated, it should throw an exception instead of returning normally.
Should a retrieval method return 'null' or throw an exception when it can't produce the return value?
Answer 1:
Whatever you do, make sure you document it. I think this point is more important than exactly which approach is "best".
Answer 2:
If you are always expecting to find a value then throw the exception if it is missing. The exception would mean that there was a problem.
If the value can be missing or present and both are valid for the application logic then return a null.
More important: What do you do other places in the code? Consistency is important.
Where should exceptions be handled?
Answer 1: in the layer of code that can actually do something about the error
Exceptions should be handled in the layer of code that can actually do something about the error.
The "log and rethrow" pattern is often considered an antipattern (for exactly the reason you mentioned, leads to a lot of duplicate code and doesn't really help you do anything practical.)
The point of an exception is that it is "not expected". If the layer of code you are working in can't do something reasonable to continue successful processing when the error happens, just let it bubble up.
If the layer of code you are working in can do something to continue when the error happens, that is the spot to handle the error. (And returning a "failed" http response code counts as a way to "continue processing". You are saving the program from crashing.)
-source: softwareengineering.stackexchange
Answer 2: Handle errors centrally, not within a middleware
Without one dedicated object for error handling, greater are the chances of important errors hiding under the radar due to improper handling. The error handler object is responsible for making the error visible, for example by writing to a well-formatted logger, sending events to some monitoring product like Sentry, Rollbar, or Raygun. Most web frameworks, like Express, provide an error handling middleware mechanism. A typical error handling flow might be: Some module throws an error -> API router catches the error -> it propagates the error to the middleware (e.g. Express, KOA) who is responsible for catching errors -> a centralized error handler is called -> the middleware is being told whether this error is an untrusted error (not operational) so it can restart the app gracefully. Note that it’s a common, yet wrong, practice to handle errors within Express middleware – doing so will not cover errors that are thrown in non-web interfaces.
-source; Handle errors centrally, not within a middleware
More: Best Practice Node.js: Error Handling
So it seems like these two principles disagree. #1 says to handle it right away if you can. So for me it would be in the service layer. But the #2 says handle it centrally, like in the server file. I went with #2.
My decision: throw the error in a custom error class
It combined a few methods people suggested. I am throwing the error, but I'm not "log and rethrow"-ing, as the answer above warned against. Instead, I put the error in a custom error with more information and throw that. It is logged and handled centrally.
So first in my service layer this is how an error is thrown:
async addUser(user) {
let newUser;
try {
newUser = await this.UserModel.create(user);
} catch (err) {
throw new ApplicationError( // custom error
{
user, // params that are useful
err, //original error
},
`Unable to create user: ${err.name}: ${err.message}` // error message
);
}
return newUser;
}
ApplicationError is a custom error class that takes an info object and a message. I got this idea from here:
In this pattern, we would start our application with an ApplicationError class this way we know all errors in our applications that we explicitly throw are going to inherit from it. So we would start off with the following error classes:
-source: smashingmagazine
You could put other helpful information in your custom error class, even maybe what EJS template to use! So you could really handle the error creatively depending on how you structure your custom error class. I don't know if that's "normal", maybe it's not SOLID to include the EJS template, but I think it's an interesting concept to explore. You could think about other ways that might be more SOLID to dynamically react to errors.
This is the handleError file for now, but I will probably change it up to work with the custom error to create a more informative page. :
const logger = require("./logger");
module.exports = (err, req, res, next) => {
if (res.headersSent) {
return next(err);
}
logger.log("Error:", err);
return res.status(500).render("500", {
title: "500",
});
};
Then I add that function to my server file as the last middleware:
app.use(handleError);
In conclusion, it seems like there's a bit of disagreement on how to handle errors though it seems more people think you should throw the error and probably handle it centrally. Find a way that works for you, be consistent, and document it.

Related

Where to find constants for nodejs google pubsub libraries?

I'm working on a service (in NodeJS) that uses Google PubSub. I'd like to do decent error handling but cannot figure out where constants might be to check error values from the library calls. E.g. I have something like:
import { PubSub, Subscription } from '#google-cloud/pubsub';
...etc...
this.pubsubClient = new PubSub();
await this.pubsubClient.createTopic(this.topicName).catch((err) => {
if (err.code === 6) {
// topic already exists
} else {
throw err;
}
});
Obviously I don't want to hardcode that 6 in there, but I cannot figure out where I should be getting a constant to check out of the Google client libraries...
(or for that matter, what type err should be. I think that would help as well.)
(also, I suppose I should be rapped on the knuckles for not checking for the topic's existence before attempting to create it. E.g. using the exception as control flow.)
The error codes come from the gRPC Status enum. The type of err is a grpc.ServiceError.
In general, it is best to try to make the exception be the exceptional case. If you expect that in most cases the topic will exist, then do a getTopic first and only create the topic if it doesn't. If the topic will usually not exist, then calls createTopic and handle the case where it already exists.

NodeJS - What's the deal with throwing errors as a means of control flow?

This question is similar, but answers are specific to sails: NodeJS best practices: Errors for flow control?
I come from a background working on REST APIs in C#. In C#, when there was an operational "error" (I'm intentionally using error in quotes), we just returned a constant or key to a lookup table of messages. Depending on the message that was returned, the routing layer would return an appropriate status code to the user (200, 404, etc.).
Take logging in. Consider that someone logs into a server, and the account does not exist. This is not an unusual thing to happen, so it doesn't make much sense to throw an error.
Now, I'm writing Node applications and I'm frustrated. It seems like common practice is to just throw errors everywhere when anything unexpected happens. This makes sense for things like the database failing to retrieve an account which is known to exist, or for catching syntax errors. But for something like a user entering the wrong username/password, throwing an error seems like overkill.
Then consider a case where you have error-reporting middleware. For syntax errors, you might want to log a stack trace, when you would never want to do that for something like a wrong password. By using errors for control flow, a user entering a bad password could be using up the same server resources as logging a critical database exception. You could extend the Error class and switch on the type of error, but that seems sloppy.
Finally, some solutions just directly return ExpressJS reponses with status codes, but I don't always want the same status code for every case where a user isn't found in the database. Maybe I want to return a 404 for a user profile page, and a 400 for a failed login.
Has anyone else faced this problem, and if so, how have you handled it (pun intended)?
UPDATE
Here's some code to demonstrate what my solution would look like.
I've invented a new class called Goof, which is like an error but it is used in situations where it is a likely outcome. For example, bad login credentials would be a type of Goof:
// constants.js.
const WRONG_PASSWORD = 1;
const USERNAME_DOES_NOT_EXIST = 1;
// goof.js.
class Goof {
constructor(message) {
this.message = message;
}
}
// Inside express route.
const username = req.body.username;
const password = req.body.password;
const result = await signInUser(username, password);
if(typeof(result) === Goof) {
if(result.status === constants.WRONG_PASSWORD) {
res.sendStatus(400);
return;
}
if(result.status === constants.USERNAME_DOES_NOT_EXIST) {
res.sendStatus(400)
return;
}
else {
throw new Error('A Goof was returned but not handled properly');
}
} else {
res.json(result);
}
What are the advantages/disadvantages of this over throwing and catching errors?

Catch and ignore/suppress errors in Mongoose post-save hook

Is it possible to catch and ignore errors in a Mongoose post-save hook, resulting in a successful return (resp. a resolved promise) from a document save call?
Sample code:
schema.post('save', function postSave(err, doc, next) {
if (err.name === 'MongoError' && err.code === 12345) {
// this does not work the way I'd expect it to
return next();
}
return next(err);
});
The above hook still results in the save call failing with the original error (next(null) doesn't help either). I can replace the error by passing a custom one to next, which shows that the mechanism is generally working, but that doesn't really help me.
The Mongoose middleware docs contain a very similar example (see the "Error Handling Middleware" section near the bottom), but don't really explain the intended behavior of the next callback.
For context, what I'm trying to accomplish in the actual project is a post-save middleware hook that retries the save call when a duplicate key error is encountered.
However, there is a special kind of post middleware called "error handling middleware" that executes specifically when an error occurs. Error handling middleware is useful for reporting errors and making error messages more readable.
I think it is too late in the hooks chain to do this. It would seem that the pre "save" hook would be a good place to check for duplicate keys no? There you can error out and then in your code re-try as you see fit.
The error handling middleware is more of an error formatting mechanism really.

What is considered standard when dealing with database errors in a REST application?

I'm currently writing a public REST service in Node.js that interfaces with a Postgres-database (using Sequelize) and a Redis cache instance.
I'm now looking into error handling and how to send informative and verbose error messages if something would happen with a request.
It struck me that I'm not quite sure how to handle internal server errors. What would be the appropriate way of dealing with this? Consider the following scenario:
I'm sending a post-request to an endpoint which in turn inserts the content to the database. However, something went wrong during this process (validation, connection issue, whatever). An error is thrown by the Sequelize-driver and I catch it.
I would argue that it is quite sensitive information (even if I remove the stack trace) and I'm not comfortable with exposing references of internal concepts (table-names, functions, etc.) to the client. I'd like to have a custom error for these scenarios that briefly describes the problem without giving away too detailed information.
Is the only way to approach this by mapping every "possible" error in the Sequelize-driver to a generic one and send that back to the client? Or how would you approach this?
Thanks in advance.
Errors are always caused by something. You should identify and intercept these causes before doing your database operation. Only cases that you think you've prepared for should reach the database operation.
If an unexpected error occurs, you should not send an informative error message for security reasons. Just send a generic error for unexpected cases.
Your code will look somewhat like this:
async databaseInsert(req, res) {
try {
if (typeof req.body.name !== 'string') {
res.status(400).send('Required field "name" was missing or malformed.')
return
}
if (problemCase2) {
res.status(400).send('Error message 2')
return
}
...
result = await ... // database operation
res.status(200).send(result)
} catch (e) {
res.status(500).send(debugging ? e : 'Unexpected error')
}
}

Handling errors at a global level

I am trying to understand how to build my error handling system for my api.
Let's say I have a the following line in a controller method :
var age = json.info.age;
with
json = {"id":1, "name":"John", info": {"age":27, "sex":"m"}}
Let's say that the object doesn't contain an info field, I'll get the following error TypeError: Cannot read property 'info' of undefined and my server will crash.
Is there a way to make a higher level abstraction and catch all the potential errors that I could have? Or should I have a try/catch system for each of the methods of my controllers?
BEWARE OF THE CODE BELOW, IT WILL BITE YOU WHENEVER IT CAN!
Don't use the code snippet below if you do not understand its
implications, please read the whole answer.
You can use the node way for uncaught errors. Add this in your config/bootstrap.js
Updated the snippet below to add what was said in the comments, also added a warning about using a global to respond to the user.
process.on('uncaughtException', function (err) {
// Handle your errors here
// global.__current__ is added via middleware
// Be aware that this is a bad practice,
// global.__current__ being a global, can change
// without advice, so you might end responding with
// serverError() to a different request than the one
// that originated the error if this one happened async
global.__current__.res.serverError();
})
Now, can doesn't mean should. It really depends on your needs, but do not try to catch BUGS in your code, try to catch at a controller level the issues that might not happen every time but are somehow expected, like a third-party service that responded with empty data, you should handle that in your controller. The uncaughtException is mainly for logging purposes, its better to let your app crash if there is a bug. Or you can do something more complicated (that might be better IMHO), which is to stop receiving requests, respond to the error 500 (or a custom one) to user that requested the faulty endpoint, and try to complete the other requests that do not relate to that controller, then log and shutdown the server. You will need several instances of sails running to avoid zero downtime, but that is material for another question. What you asked is how to get uncaught exceptions at a higher lvl than the controllers.
I suggest you read the node guide for error handling
Also read about domains, even thought they are deprecated you can use them, but you would have to deal with them per controller action, since sails does not provide any help with that.
I hope it helps.
You can check this way if you want to:
if (object != null && object.response != null && object.response.docs != null){
//Do your stuff here with your document
}
I don't really get what is your "object" variable in the first place, so i don't know if you can check it at a different level, is it a sails parameter to your controller ?
So that's how I did it, thanks to Zagen's answer.
module.exports.bootstrap = function(cb) {
process.on('uncaughtException', function (err) {
//Handle your errors here
logger.fatal(err);
global.__current__.res.serverError();
})
cb();
};
I send a generic error 500 to the user if any uncaught exception is thrown, and I log the error to the fatal level. On that way, my server is still accessible 24/7 and I can monitor the logs at another level and trigger an alarm on a fatal error. I can then fix the exception that was thrown.

Resources