Node.js Error Handling Best Practices with Examples


NodeJS

Author: Steve Alila
Reviewer: Deepak Prasad

An error is an object with a description denoting that something went wrong. Whether you want to handle runtime or syntax errors, this tutorial will help you out.

The tutorial simplifies Node.js error handling in synchronous and asynchronous (callback and promised-based) code using Node.js process module, try-catch block, and then-catch block.

It would be helpful to acquire basic Node.js knowledge, understand promises and install the latest version of Node.js and Visual Studio Code before proceeding with this tutorial.

Let's get started.

 

Lab setup to practice Node.js error handling

You can open the file using your preferred code editor in the examples section. I am using Visual Studio Code on Ubuntu.

Make the project directory and cd into it.

mkdir errorHandling && cd errorHandling

Next, create sync.js, callback.js, and promises.js for handling errors related to the synchronous, callback, and promise-based asynchronous code, respectively.

touch sync.js callback.js promises.js
code .

Node.js Error Handling Best Practices with Examples

 

Example~1: Node.js error handling in synchronous code

A try-catch block is the most typical way for Node.js error handling in synchronous code. For example, assume we try reading a file that does not exist.

Input

const fs = require("fs")

try {
    const output = fs.readFileSync('index.html', 'utf-8')
    console.log(output)
} catch {
    console.log("There was an error reading the file.")
}

We import the fs module. In thetry block, we attempt to read the file using the fs.readFileSync() method. And console-log the output. If the process fails, it throws an error object. The catch block receives the error object through the given parameter. We could print the error object, but we printed a custom message, "There was an error reading the file."

Using the node command, run the file on the terminal.

node sync.js

Output

We get the expected error.

$ node sync.js
There was an error reading the file.

Now, let's print the error object.

Input

const fs = require("fs")

try {
    const output = fs.readFileSync('index.html', 'utf-8')
    console.log(output)
} catch (error) {
    console.log(error)
}

Output

$ node sync.js
Error: ENOENT: no such file or directory, open 'index.html'
    at Object.openSync (node:fs:599:3)
    at Object.readFileSync (node:fs:467:35)
    at Object.<anonymous> (/home/user/errorHandling/sync.js:4:23)
    at Module._compile (node:internal/modules/cjs/loader:1119:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1173:10)
    at Module.load (node:internal/modules/cjs/loader:997:32)
    at Module._load (node:internal/modules/cjs/loader:838:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:18:47 {
  errno: -2,
  syscall: 'open',
  code: 'ENOENT',
  path: 'index.html'
}

The following line tells us that the program caught an error object denoting that the requested file does not exist.

Error: ENOENT: no such file or directory, open 'index.html'

The program gives more hints about the error, like its code and the requested file path.

Node.js error handling

 

Example~2: Node.js error handling using a callback function + process module

Most Node.js native modules accommodate asynchronous programming through a callback function. The first parameter to the callback function is the error object.

 

Use a callback function + process module

Let's create the index.html file that did not exist in Example~1

cat >> index.html

Add some content to it.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

Then, attempt to read its content asynchronously with a misspelled name.

Input

const fs = require("fs")
fs.readFile('endix.html', 'utf-8', (error, data) => {
    if (error) throw error
    console.log(data)
})

Unless the error is handled, the throw keyword halts the execution of subsequent code portions. We can handle the error using the process module.

process.on("uncaughtException", error => error ? console.log(error) : console.log(""))

The process.on() method listens for the uncaughtException event. It then prints the error or an empty string.

Output

$ node callback.js 
[Error: ENOENT: no such file or directory, open 'endix.html'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'endix.html'
}

Node.js Error Handling Best Practices with Examples

Alternatively, we can do some computations and handle the custom error with a callback function.

 

Use a callback function only

Assume we want to throw an error object whenever a user attempts to divide a whole number with zero.

Input

const calculate = (x, y, callback) => 
y === 0 
? 
callback(new Error("Fatal error: Attempting to divide by zero")) 
: 
callback(null, x/y)
 
calculate(3, 0, (err, result) => err ? console.log(err) : console.log(result))

The calculate() function accepts the first number x, the second number y, and a callback function. If y is zero, we throw a custom error object with the message, "Fatal error: Attempting to divide by zero". Otherwise, the callback function returns no error and the result of dividing x with y.

We then attempt to divide 3 by 0 and get the custom error object.

Output

$ node callback.js 
Error: Fatal error: Attempting to divide by zero
    at calculate (/home/user/errorHandling/callback.js:4:10)
    at Object.<anonymous> (/home/user/errorHandling/callback.js:8:1)
    at Module._compile (node:internal/modules/cjs/loader:1119:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1173:10)
    at Module.load (node:internal/modules/cjs/loader:997:32)
    at Module._load (node:internal/modules/cjs/loader:838:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:18:47

Node.js Error Handling Best Practices with Examples

 

Example~3: Node.js error handling in promises

We can handle promise-based errors using then-catch or try-catch blocks in an async-await function.

const returnAPromise = () => {
    return new Promise( (resolve, reject) => {
       let didSomething = false
       didSomething ? resolve('OK') : reject('Mission failed')
    })
}

// then-catch
returnAPromise()
.then( result => console.log(result)) 
.catch( error => console.log(error)) 

// async-await and try-catch
const showOutput = async () => {
    try {
        const data = await returnAPromise()
        console.log(data)
    } catch (error) {
        console.log(error)
    }
}
showOutput()

The returnAPromise() function returns a promise. If the didSomething condition is met, it resolves the promise with the OK message. Otherwise, it rejects the promise with a Mission failed message.

The then() method prints the success message. On the other hand, the catch() method handles errors that the then block(s) may throw. Finally, it prints the error message. The then-catch methods work similarly to try-catch in async-await functions.

Output

$ node promises.js 
Mission failed
Mission failed

The first Mission failed originated from the then-catch block, whereas the second Mission failed resulted from the try-catch block.

Additionally, we can throw a custom error inside the async-await function and handle it with the then-catch block. Assume we misspell a part of the URL when requesting remote data with the fetch API.

Input

const getUsers = async () => {
    const res = await fetch("https://jsonplaceholder.typicode.com/userss")
    if (res.status !== 200) throw new Error("Failed to get users")
    const data = await res.json()
    return data
}

getUsers()
    .then( users => console.log(users))
    .catch( error => console.log(error))

Although the target URL is https://jsonplaceholder.typicode.com/users, we accidentally input https://jsonplaceholder.typicode.com/userss. We check whether the request is successful. Otherwise, we throw a custom error.

if (res.status !== 200) throw new Error("Failed to get users")

The program will not attempt to retrieve users from the response object if it encounters an error. Instead, the promise fails, throwing the error. We then handle the error in the catch block.

.catch(error => console.log(error))

Output

$ node promises.js 
(node:2846) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Error: Failed to get users
    at getUsers (/home/user/errorHandling/promises.js:3:35)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Node.js Error Handling Best Practices with Examples

 

Conclusion

In this tutorial, you learned Node.js error handling in synchronous and asynchronous code using practical examples. Now that you have multiple options to choose from when handling errors, it is your turn to apply the most applicable approach.

 

Steve Alila

Steve Alila

He specializes in web design, WordPress development, and data analysis, with proficiency in Python, JavaScript, and data extraction tools. Additionally, he excels in web API development, AI integration, and data presentation using Matplotlib and Plotly. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment