Error Handling in Node.js

By Syncfusion®, Inc. · Last updated

Reading Node.js errors and stack traces, error.cause, system errors with code, errno, syscall and path, common error codes, Node.js ERR_ codes, operational versus programmer errors, thrown exceptions, rejected promises and error events, custom error classes, and process-level handlers: uncaughtException, unhandledRejection, the warning event and the fail-fast strategy.

Error Handling

Error = an object that says what went wrong and where.

  • In Node.js, errors come from your code, from Node.js itself and from the operating system.
  • A good program expects some errors, such as a missing file.
  • It handles those calmly and stops cleanly on the rest.

Simple lesson: errors are messages. Read them, handle the expected ones, and stop on the strange ones.

A Real-World Example

Think of a school bus.

  • A flat tyre on the way: the driver changes it and the trip goes on. That is expected.
  • The brakes fail: the driver stops the bus at once and calls for help. Going on would be dangerous.
  • Either way, the driver writes down what happened in the logbook.

In Node.js, a missing file is the flat tyre. A bug in your code is the failed brakes.

You learned try, catch, finally, throw and the basic error types in Error and Exception Handling in the JavaScript course. This lesson adds what Node.js gives you on top.

What You Will Learn

In this lesson, you will learn:

  1. How to read a stack trace and error.cause
  2. System errors and their code, errno, syscall and path
  3. Common error codes such as ENOENT and EADDRINUSE
  4. Node.js ERR_ codes
  5. Operational errors vs programmer errors
  6. Thrown errors, rejected promises and error events
  7. Custom error classes with codes and context
  8. uncaughtException, unhandledRejection and the warning event
  9. Why a program should fail fast

Reading Errors

Error Classes and Stack Traces

Stack trace = the list of function calls that led to the error, newest first.

// main.mjs
function readMarks(name) {
  return getRegister()[name].maths;
}

function getRegister() {
  return { Asha: { maths: 91 } };
}

function printReport() {
  console.log(readMarks("Ravi"));
}

printReport();

Output:

file:///home/asha/school/main.mjs:3
  return getRegister()[name].maths;
                            ^

TypeError: Cannot read properties of undefined (reading 'maths')
    at readMarks (file:///home/asha/school/main.mjs:3:29)
    at printReport (file:///home/asha/school/main.mjs:11:15)
    at file:///home/asha/school/main.mjs:14:1

Node.js v22.20.0

Reading a Stack Trace Step by Step

  1. The top lines show the file, the line number and the exact code, with ^ under the problem.
  2. The error line gives the type and the message: TypeError: Cannot read properties of undefined.
  3. The first at line is where it broke: readMarks, line 3.
  4. The next at lines show who called it: printReport, then the main file.
  5. Start fixing at the first line that is your file.

Lines that mention node:internal are inside Node.js. Skip them and look for your own file names.

Here, getRegister()["Ravi"] is undefined, because Ravi is not in the register.

error.cause

cause = the original error, kept inside a new, clearer error.

Simple lesson: read the error line first, then the first at line in your own file.

Error Types

System Errors

System error = an error from the operating system, such as "file not found".

Node.js adds extra facts to these errors:

  • code = a short name, such as ENOENT. Check this in your code.
  • errno = the same problem as a number, such as -2.
  • syscall = which system job failed, such as open.
  • path = which file it was about.

Common Codes

CodeMeansUsual fix
ENOENTNo such file or folderCheck the path, or create the file
EACCESPermission deniedUse a folder you may write to
EEXISTIt already existsUse { recursive: true } or another name
EADDRINUSEThe port is already in useStop the other program, or pick another port
ECONNREFUSEDNothing is listening at that addressStart the other server first
ECONNRESETThe other side closed the connectionRetry, and check the other server
ETIMEDOUTThe other side did not answer in timeRetry later, or raise the timeout
EMFILEToo many open filesClose files, or open fewer at once

Node.js ERR_ Codes

  • Errors made by Node.js itself have codes that start with ERR_.
  • Examples: ERR_INVALID_ARG_TYPE, ERR_MODULE_NOT_FOUND, ERR_HTTP_HEADERS_SENT.
  • Search the code name in the Node.js docs to learn the cause.
// main.mjs
import { readFileSync } from "node:fs";
readFileSync(42.5);

Output:

node:fs:561
    getValidatedPath(path),
    ^

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of Buffer or URL. Received type number (42.5)
    at Object.openSync (node:fs:561:5)
    at readFileSync (node:fs:444:35)
    at file:///home/asha/school/main.mjs:3:1 {
  code: 'ERR_INVALID_ARG_TYPE'
}

Node.js v22.20.0

Operational vs Programmer Errors

  • Operational error = a problem from the outside world. Expect it and handle it.

    • a missing file, a server that is down, bad user input
  • Programmer error = a bug in the code. Fix the code. Do not hide it.

    • reading a property of undefined, calling a function with the wrong type

Simple lesson: check err.code for outside problems, and fix the code for bugs.

Error Delivery Mechanisms

An error reaches you in one of three ways:

  • Thrown exception = from sync code. Catch it with try/catch.
  • Rejected promise = from async code. Catch it with await inside try/catch, or with .catch().
  • error event = from an emitter, such as a stream or a server. Catch it with on("error", ...).

Choosing the Right Mechanism

  • Sync function that fails: throw.
  • Async function that fails: reject (throwing inside an async function does this).
  • Long-running object with many results: emit error.
  • Never mix them in one function. Callers would not know which one to catch.

Simple lesson: try/catch for throws and awaits, and on("error") for emitters.

Four ways an error arrives, and where you catch each one How the error arrivesWhere you catch it throw new Error() a rejected promise callback(err, data) emitter.emit("error", err) try / catch await in try, or .catch() if (err) first on("error", ...)

Each way an error can arrive has its own way to catch it.

Custom Errors

A custom error class gives an error a clear name, a code and extra facts.

  • this.name = the class name shown in logs.
  • code = a short id that code can check. Messages may change, codes should not.
  • Extra fields such as roll = context for the person fixing the problem.
  • else throw err = only catch what you can handle. Pass the rest up.

An empty catch {} hides bugs. Always log, handle or rethrow the error.

Simple lesson: give your errors a name, a code and the facts needed to fix them.

Process-Level Handlers

These are the last safety nets on the process object.

  • uncaughtException = an error that nobody caught.
  • uncaughtExceptionMonitor = see the error just before the crash, without stopping it. Good for logging.
  • unhandledRejection = a rejected promise that nobody caught.
  • warning = a warning from Node.js, such as a deprecation.
  • --unhandled-rejections=mode = a flag that changes what happens with unhandled rejections. The default is throw: they crash the program.

Why You Should Not Continue After uncaughtException

  • An uncaught error means the program is in an unknown state.
  • A file may be half written. A counter may be wrong. A connection may be stuck.
  • Carrying on can make wrong results, which is worse than stopping.
  • Fail fast = log the error, clean up quickly, exit with a non-zero code.
  • A process manager then starts a fresh, clean copy (see Production Readiness).

Animation: an uncaught error is logged, the process exits, and a process manager starts a fresh copy uncaught error log it exit(1) fresh startby the manager

Fail fast: log, exit, and let a manager start a clean copy.

Simple lesson: use process handlers to log and exit, never to ignore errors and carry on.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
An empty catch {}Hiding errors keeps the app runningHandle it, or log and rethrowBugs stay hidden and data goes wrong
Checking err.message textMessages are stableCheck err.codeA small message change breaks your code
Calling an async function without await or .catchErrors still reach try/catchAlways await or catch promisesAn unhandled rejection crashes the program
Keeping the app alive in uncaughtExceptionThe handler fixes the problemLog, clean up, exitThe program runs in a broken state
Throwing strings: throw "bad"Any value is fineThrow Error objectsNo stack trace, no name, no code
Losing the original error when wrappingThe new message is enoughUse { cause: err }Nobody can find the real reason

TL;DR

  1. Stack trace = read the error line, then the first at line in your file.
  2. cause = keeps the original error inside a new one.
  3. System error = has code, errno, syscall and path.

    • ENOENT missing, EACCES not allowed, EADDRINUSE port busy
  4. ERR_ codes = errors made by Node.js itself.
  5. Operational error = handle it. Programmer error = fix the code.
  6. Throw for sync, reject for async, error event for emitters.
  7. Custom error = a class with name, code and context.
  8. Fail fast = after an uncaught error, log and exit.

Check Yourself

Exercises

What to Remember

  • Read an error from the message line, then the first at line in your own file.
  • System errors carry code, errno, syscall and path. Check code.
  • Handle operational errors. Fix programmer errors.
  • try/catch for throws and awaits, on("error") for emitters.
  • Custom errors need a name, a code and context. Use cause when wrapping.
  • After an uncaught error, log and exit. Do not carry on.

Next: Process, OS and CLI Tools. You will read command-line input, environment variables and signals.