Node js error handling
Major security implementations in Node.js are:
Authentications
Error Handling
Node.js is a JavaScript extension used for server-side scripting. Error handling is a mandatory step in application development. A Node.js developer may work with both synchronous and asynchronous functions simultaneously. Handling errors in asynchronous functions is important because their behavior may vary, unlike synchronous functions. While try-catch blocks are effective for synchronous functions, asynchronous functions can be dealt with callbacks, promises, and async-await. Try-catch is synchronous means that if an asynchronous function throws an error in a synchronous try/catch block, no error throws. Errors thrown in Node.js applications can be handled in the following ways:
Using try-catch block
Using callbacks
Using promises and promise callbacks
Using async-await
Why? For your own sanity. You want to make bug fixing less painful. It helps you write cleaner code. It centralizes all errors and lets you enable alerting and notifications so you know when and how your code breaks.
Once you have a set of custom errors, you can configure centralized error handling. You want to have a middleware that catches all errors. There you can decide what to do with them and where to send them if they need to notify you via an alert notification.
In your API routes you’ll end up using the next() function to forward errors to the error handler middleware.
Let me show you.
...
app.post('/user', async (req, res, next) => {
try {
const newUser = User.create(req.body)
} catch (error) {
next(error)
}
})
...
The next() function is a special function in Express.js middlewares that sends values down the middleware chain. At the bottom of your routes files you should have a .use() method that uses the error handler middleware function.
const { logError, returnError } = require('./errorHandler')
app.use(logError)
app.use(returnError)
The error handler middleware should have a few key parts. You should check if the error is operational, and decide which errors to send as alert notifications so you can debug them in more detail. Here’s what I suggest you add to your error handler.
function logError (err) {
console.error(err)
}
function logErrorMiddleware (err, req, res, next) {
logError(err)
next(err)
}
function returnError (err, req, res, next) {
res.status(err.statusCode || 500).send(err.message)
}
function isOperationalError(error) {
if (error instanceof BaseError) {
return error.isOperational
}
return false
}
module.exports = {
logError,
logErrorMiddleware,
returnError,
isOperationalError
}
Everything I’ve explained so far has been related to operational errors. I’ve shown how to gracefully handle expected errors and how to send them down the middleware chain to a custom error handling middleware.
Let’s jump into programmer errors now. These errors can often cause issues in your apps like memory leaks and high CPU usage. The best thing to do is to crash the app and restart it gracefully by using the Node.js cluster mode or a tool like PM2.
Promise rejections in Node.js only cause warnings. You want them to throw errors, so you can handle them properly.
It’s good practice to use fallback and subscribe to:
process.on('unhandledRejection', callback)
This lets you throw an error properly.
Here’s what the error handling flow should look like.
...
const user = User.getUserById(req.params.id)
.then(user => user)
// missing a .catch() block
...
// if the Promise is rejected this will catch it
process.on('unhandledRejection', error => {
throw error
})
process.on('uncaughtException', error => {
logError(error)
if (!isOperationalError(error)) {
process.exit(1)
}
})
Whatever you do, choose one way to deliver operational errors. You can throw errors and deliver them synchronously, or asynchronously by using Promise rejections, passing them in callbacks, or emitting errors on an EventEmitter.