# Error Handling

Course: [Node.js](https://www.devtutorials.ai/learn/nodejs) · Section: Asynchronous Node.js
Source: https://www.devtutorials.ai/learn/nodejs/async/error-handling

> 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.

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

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

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

printReport();
```

Output:

```text
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

<div className="tscroll">

| Code | Means | Usual fix |
|---|---|---|
| `ENOENT` | No such file or folder | Check the path, or create the file |
| `EACCES` | Permission denied | Use a folder you may write to |
| `EEXIST` | It already exists | Use `{ recursive: true }` or another name |
| `EADDRINUSE` | The port is already in use | Stop the other program, or pick another port |
| `ECONNREFUSED` | Nothing is listening at that address | Start the other server first |
| `ECONNRESET` | The other side closed the connection | Retry, and check the other server |
| `ETIMEDOUT` | The other side did not answer in time | Retry later, or raise the timeout |
| `EMFILE` | Too many open files | Close files, or open fewer at once |

</div>

### 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.

```js
// main.mjs
import { readFileSync } from "node:fs";
readFileSync(42.5);
```

Output:

```text
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.

<figure className="ngc-fig node-fig">
<svg viewBox="0 0 560 210" role="img" ariaLabelledby="fig-node-errpaths" style={{ width: "100%", maxWidth: 560, height: "auto" }}>
<title id="fig-node-errpaths">Four ways an error arrives, and where you catch each one</title>
<text x="20" y="24" style={{ fill: "var(--ink)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>How the error arrives</text><text x="330" y="24" style={{ fill: "var(--ink)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>Where you catch it</text>
<rect x="20" y="36" width="250" height="34" rx="8"  style={{ fill: "var(--ngc-red-soft)", stroke: "var(--ngc-red)" }}/><text x="32" y="58" style={{ fill: "var(--ngc-red)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>throw new Error()</text>
<rect x="20" y="78" width="250" height="34" rx="8"  style={{ fill: "var(--ngc-red-soft)", stroke: "var(--ngc-red)" }}/><text x="32" y="100" style={{ fill: "var(--ngc-red)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>a rejected promise</text>
<rect x="20" y="120" width="250" height="34" rx="8"  style={{ fill: "var(--ngc-red-soft)", stroke: "var(--ngc-red)" }}/><text x="32" y="142" style={{ fill: "var(--ngc-red)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>callback(err, data)</text>
<rect x="20" y="162" width="250" height="34" rx="8"  style={{ fill: "var(--ngc-red-soft)", stroke: "var(--ngc-red)" }}/><text x="32" y="184" style={{ fill: "var(--ngc-red)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>emitter.emit("error", err)</text>
<rect x="330" y="36" width="210" height="34" rx="8"  style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)" }}/><text x="342" y="58" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>try / catch</text>
<rect x="330" y="78" width="210" height="34" rx="8"  style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)" }}/><text x="342" y="100" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>await in try, or .catch()</text>
<rect x="330" y="120" width="210" height="34" rx="8"  style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)" }}/><text x="342" y="142" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>if (err) first</text>
<rect x="330" y="162" width="210" height="34" rx="8"  style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)" }}/><text x="342" y="184" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>on("error", ...)</text>
<g style={{ stroke: "var(--ink)", strokeWidth: 1.5 }}><line x1="270" y1="53" x2="330" y2="53" /><line x1="270" y1="95" x2="330" y2="95" /><line x1="270" y1="137" x2="330" y2="137" /><line x1="270" y1="179" x2="330" y2="179" />
</g>
<circle r="6" cx="270" cy="53" style={{ fill: "var(--ngc-orange)" }}><animate attributeName="cy" values="53;53;95;95;137;137;179;179" keyTimes="0;0.22;0.25;0.47;0.5;0.72;0.75;1" dur="8s" repeatCount="indefinite" /><animate attributeName="cx" values="270;330;270;330;270;330;270;330" keyTimes="0;0.22;0.25;0.47;0.5;0.72;0.75;1" dur="8s" repeatCount="indefinite" /></circle>
</svg>
<figcaption>Each way an error can arrive has its own way to catch it.</figcaption>
</figure>

## 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**).

<figure className="ngc-fig node-fig">
<svg viewBox="0 0 560 170" role="img" ariaLabelledby="fig-node-failfast" style={{ width: "100%", maxWidth: 560, height: "auto" }}>
<title id="fig-node-failfast">Animation: an uncaught error is logged, the process exits, and a process manager starts a fresh copy</title>
<rect x="20" y="60" width="110" height="50" rx="10"  style={{ fill: "var(--ngc-red-soft)", stroke: "var(--ngc-red)", strokeWidth: 1.5 }}/><text x="75" y="90" textAnchor="middle" style={{ fill: "var(--ngc-red)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>uncaught error</text>
<rect x="160" y="60" width="100" height="50" rx="10"  style={{ fill: "var(--ngc-orange-soft)", stroke: "var(--ngc-orange)", strokeWidth: 1.5 }}/><text x="210" y="90" textAnchor="middle" style={{ fill: "var(--ngc-orange)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>log it</text>
<rect x="290" y="60" width="100" height="50" rx="10"  style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)", strokeWidth: 1.5 }}/><text x="340" y="90" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>exit(1)</text>
<rect x="420" y="60" width="120" height="50" rx="10"  style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)", strokeWidth: 1.5 }}/><text x="480" y="85" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>fresh start</text><text x="480" y="102" textAnchor="middle" style={{ fill: "var(--ink)", fontFamily: "var(--sans)", fontSize: 11 }}>by the manager</text>
<circle r="7" cx="75" cy="135" style={{ fill: "var(--ngc-red)" }}>
<animate attributeName="cx" values="75;210;340;480;480" keyTimes="0;0.25;0.5;0.75;1" dur="5s" repeatCount="indefinite" />
<animate attributeName="fill" values="var(--ngc-red);var(--ngc-orange);var(--ngc-purple);var(--ngc-green);var(--ngc-green)" keyTimes="0;0.25;0.5;0.75;1" dur="5s" repeatCount="indefinite" />
</circle>
</svg>
<figcaption>Fail fast: log, exit, and let a manager start a clean copy.</figcaption>
</figure>

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

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| An empty `catch {}` | Hiding errors keeps the app running | Handle it, or log and rethrow | Bugs stay hidden and data goes wrong |
| Checking `err.message` text | Messages are stable | Check `err.code` | A small message change breaks your code |
| Calling an async function without `await` or `.catch` | Errors still reach `try`/`catch` | Always await or catch promises | An unhandled rejection crashes the program |
| Keeping the app alive in `uncaughtException` | The handler fixes the problem | Log, clean up, exit | The program runs in a broken state |
| Throwing strings: `throw "bad"` | Any value is fine | Throw `Error` objects | No stack trace, no name, no code |
| Losing the original error when wrapping | The new message is enough | Use `{ cause: err }` | Nobody can find the real reason |

</div>

## 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.
