Like any kind of apps, JavaScript apps also have to be written well.
Otherwise, we run into all kinds of issues later on.
In this article, we’ll look at some best practices we should follow when writing Node apps.
Use Environment Aware, Secure and Hierarchical Config
We should make our app configurable.
To do that, we can read the settings from environment variables.
For instance, we can use the dotenv library to read environment variables from a .env
file and other sources.
This will make our app run properly anywhere.
Use Async-Await or Promises for Async Error Handling
Async code should be used for any long-running operation.
The worst way to chain async code is to have multiple levels of nested async callbacks.
They’re hard to read and debug.
The best way is to use promises and chain them.
To make them even shorter, we can use async and await.
So either, we write:
return functionA()
.then(functionB)
.then(functionC)
.then(functionD)
.catch((err) => console.error(err))
.then(alwaysRunThisFunction)
or:
async function executeAsyncTask () {
try {
const valueA = await functionA();
const valueB = await functionB(valueA);
const valueC = await functionC(valueB);
return await functionD(valueC);
}
catch (err) {
console.error(err);
}
finally {
await alwaysRunThisFunction();
}
}
They do the same thing.
The callbacks all return promises so we can chain them.
Use Only the Built-in Error Object
When we throw errors, we should use the built-in error type.
For instance, we should write:
new Error('whoops!')
instead of:
throw ('whoops!');
The Error
instance has information like stack traces that aren’t available anywhere else.
Distinguish Operational vs Programmer Errors
We should distinguish operational errors like API errors where they can be handled properly.
Programmer errors are errors like undefined errors that we didn’t take into account.
Programmer errors should be fixed so that our app will run smoother.
Operational errors should be handled by our code.
Handle Errors Centrally, not Within an Express Middleware
Errors should be handled centrally rather than with an Express middleware.
Otherwise, we’ll get duplicated code in multiple places to handle the same error.
For example, instead of writing:
app.use((err, req, res, next) => {
logger.logError(err);
if (err.severity == errors.high) {
mailer.sendMail(configuration.adminMail, 'error occured', err);
}
next(err);
});
We write:
module.exports.handler = new ErrorHandler();
function ErrorHandler() {
this.handleError = async (err) => {
await logger.logError(err);
await sendEmail;
await saveInOpsQueueIfCritical;
await determineIfOperationalError;
};
}
We have a central error handler we can import into other modules.
Document API Errors Using Swagger or GraphQL
We should document the error s that may occur with our REST APIs with Swagger or GraphQL.
This way, we can use the API and handle the errors that are documented.
If we handle the errors that are encountered, then it won’t crash.
Exit the Process Gracefully
If an unknown error is encountered, we should exit the process gracefully with a process manager.
This can be done with a process manager with Forever or PM2.
These tools can restart our app if any errors occur.
Conclusion
We should document any errors that APIs can throw so they can be handled.
Async code and making our app configurable are also good ideas.