# An Introduction to Node.js Debugging

Node.js is a popular JavaScript runtime with the same V8 engine used in Google's Chrome browser. It's cross-platform and has become increasingly popular for creating server-side web applications, build tools, command-line apps, and more.

Node.js books and tutorials often explain debugging toward the end. It's often regarded as an advanced technique for expert developers. This can lead to frustration when you encounter a problem at the start of your coding journey.

If you're lucky, your code will crash with an obvious error message. If you're unlucky, your application will run but not generate the results you expect.

## What is Debugging?

**Debugging** is the art of fixing software defects. Fixing a bug is often straightforward; a corrected character or edited line of code solves the problem. *Finding* the cause of a bug is another matter. Developers often spend frustrating hours locating the source of an issue.

There are ways you can avoid errors:

1. Use a good code editor with line numbering, color-coding, linting, auto-completion, bracket matching, parameter prompting, and more.
    
2. Use a source control system such as [Git](https://git-scm.com/) to manage code revisions. The tools can help others to check updates and locate how, when, and where a bug appeared.
    
3. Adopt bug-tracking systems such as [Jira](https://www.atlassian.com/software/jira), [FogBugz](https://fogbugz.com/), or [Bugzilla](https://www.bugzilla.org/). Use these to report bugs, highlight duplicates, document reproduction steps, determine severity, calculate priorities, assign developers, record discussions, and track fix progress.
    
4. Use Test-Driven Development. TDD is a development process which encourages developers to write code to test the operation of a function *before* it's written.
    
5. Try techniques such as code explaining or pair programming to work with other developers. A fresh set of eyes may spot issues you'd not considered.
    

No solution will eradicate all errors and you'll encounter the following types in all programming languages.

### Syntax errors

An error is caused when your code fails to follow the rules of a language. Typically, it could be a misspelt statement or a missing bracket.

Good code editors such as [VS Code](https://code.visualstudio.com/) help spot common Node.js problems before you attempt to run it:

* color-coding valid and invalid statements
    
* auto-completing function and variable names
    
* highlighting matching brackets
    
* auto-indenting code blocks
    
* parameter prompts for functions, properties, and methods
    
* detecting unreachable code
    
* refactoring messy functions
    

You can also use a code linter such as [ESLint](https://eslint.org/) to spot other syntactical problems or deviations from your normal coding style. Install ESLint as a global Node.js module with:

```sh
npm i eslint -g
```

Then check JavaScript files from the command line:

```sh
eslint code.js
```

It's easier to use the [ESLint for VS Code extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) which validates code as you type:

![VS Code ESLint extension](https://i.imgur.com/Jn9sDpw.png align="left")

### Logic errors

A logic error means your code runs but fails to work as you expect. For example, a user cannot log in with valid credentials, a report shows incorrect statistics, user data is not saved to a database, etc. The cause could be anything:

* using an incorrect variable name
    
* using incorrect conditions, e.g. `if (x > 5)` rather than `if (x < 5)`
    
* invalid functions, parameters, or algorithms
    

It's often necessary to step through code and examine the state at specific points during execution.

### Runtime errors

A runtime error becomes evident when the application executes. It may not happen every time and could result from invalid user input e.g.

* attempting to divide a value by zero
    
* accessing array items or database records which no longer exist
    
* attempting to write to a file without appropriate access permissions
    
* incorrect [asynchronous function](https://blog.openreplay.com/javascript-async-programming-tips-tricks-and-gotchas/) implementations which lead to "memory overflow" crashes.
    

Runtime errors are notoriously difficult to replicate so good logging is essential.

## Node.js Debugging Environment Variables

[Environment variables](https://nodejs.org/api/cli.html#environment-variables) set within the host operating system control Node.js application settings. The most common is `NODE_ENV`, which is typically set to `development` when debugging or `production` on a live server.

You can set environment variables on Linux/macOS:

```sh
NODE_ENV=development
```

at the Windows (legacy DOS) command prompt:

```sh
set NODE_ENV=development
```

or Windows Powershell:

```sh
$env:NODE_ENV="development"
```

Your application can detect environment settings and enable debugging messages when necessary. For example:

```javascript
// running in development mode?
const DEVMODE = (process.env.NODE_ENV === 'development');

if (DEVMODE) {
  console.log('application started in development mode');
}
```

`NODE_DEBUG` enables debugging messages using the Node.js `util.debuglog`. (See the [Node.js `util.debuglog`](#nodejs-utildebuglog) section below). Also, check the documentation of your primary modules and frameworks to discover further logging options.

## Debug with Node.js Command-line Options

You can pass [command-line options](https://nodejs.org/api/cli.html) to the `node` or `nodemon` runtime when launching your application. One of the most useful is `--trace-warnings`, which outputs a stack traces when a promise fails to resolve or reject:

```sh
node --trace-warnings index.js
```

Other options include:

* `--enable-source-maps`: enable source maps when using a transpiler such as TypeScript
    
* `--throw-deprecation`: throw errors when using deprecated features
    
* `--inspect`: activate the V8 inspector (see the [Node.js V8 Inspector](#nodejs-v8-inspector) section below).
    

## Debug with Console Logging

The easiest way to debug an application is to output values to the console during execution:

```javascript
console.log(`myVariable: ${ myVariable }`);
```

Some developers claim you should never use `console.log()` because you're changing code and there are better debugging options. This is true -- *but everyone does it!* Use whatever tool makes you productive. Console logging can be a quick and practical option. Finding and fixing a bug is more important than the method you used to find it.

You should consider [other options](https://nodejs.org/api/console.html) beyond the standard `console.log()` command:

| `console` method | Description |
| --- | --- |
| [`.log(msg)`](https://nodejs.org/api/console.html#console_console_log_data_args) | output a message to the console |
| `.log('%j', obj)` | output an object as a compact JSON string |
| [`.dir(obj,opt)`](https://nodejs.org/api/console.html#console_console_dir_obj_options) | uses [`util.inspect`](https://nodejs.org/api/util.html#utilinspectobject-options) to pretty-print objects and properties |
| [`.table(obj)`](https://nodejs.org/api/console.html#console_console_table_tabulardata_properties) | outputs arrays of objects in tabular format |
| [`.error(msg)`](https://nodejs.org/api/console.html#console_console_error_data_args) | output an error message |
| [`.count(label)`](https://nodejs.org/api/console.html#console_console_count_label) | a named counter reporting the number of times the line has executed |
| [`.countReset[label]`](https://nodejs.org/api/console.html#console_console_countreset_label) | resets a named counter |
| [`.group(label)`](https://nodejs.org/api/console.html#console_console_group_label) | indents a group of log messages |
| [`.groupEnd(label)`](https://nodejs.org/api/console.html#console_console_groupend) | ends the indented group |
| [`.time(label)`](https://nodejs.org/api/console.html#console_console_time_label) | starts a timer to calculate the duration of an operation |
| `.timeLog(label)` | reports the elapsed time since the timer started |
| [`.timeEnd(label)`](https://nodejs.org/api/console.html#console_console_timeend_label) | stops the timer and reports the total duration |
| [`.trace()`](https://nodejs.org/api/console.html#console_console_trace_message_args) | outputs a stack trace (a list of all calling functions) |
| [`.clear()`](https://nodejs.org/api/console.html#console_console_clear) | clear the console |

`console.log()` accepts a list of comma-separated values. For example:

```javascript
let x = 123;
console.log('x:', x);
// x: 123
```

ES6 destructuring offers similar output with less effort:

```javascript
console.log({ x });
// { x: 123 }
```

[`util.inspect`](https://nodejs.org/api/util.html#utilinspectobject-options) can format objects for easier reading but `console.dir()` does the hard work for you:

```javascript
console.dir(myObject, { depth: null, color: true });
```

## Node.js `util.debuglog`

The Node.js standard `util` module offers a [`debuglog`](https://nodejs.org/api/util.html#utildebuglogsection-callback) method which conditionally writes log messages to `STDERR`:

```javascript
const util = require('util');
const debuglog = util.debuglog('myapp');

debuglog('myapp debug message [%d]', 123);
```

The console displays these debugging messages When you set the `NODE_DEBUG` environment variable to `myapp` or a wildcard such as `*` or `my*`:

```sh
MYAPP 4321: myapp debug message [123]
```

where `4321` is the Node.js process ID.

## Debug with Logging Modules

Third-party logging modules are available for Node.js. These offer further options for messaging levels, verbosity, sorting, file output, profiling, reporting, and more:

* [cabin](https://www.npmjs.com/package/cabin)
    
* [loglevel](https://www.npmjs.com/package/loglevel)
    
* [morgan](https://www.npmjs.com/package/morgan) (Express.js middleware)
    
* [pino](https://www.npmjs.com/package/pino)
    
* [signale](https://www.npmjs.com/package/signale)
    
* [storyboard](https://www.npmjs.com/package/storyboard)
    
* [tracer](https://www.npmjs.com/package/tracer)
    
* [winston](https://www.npmjs.com/package/winston)
    

## Debug with the Node.js V8 Inspector

Node.js is a wrapper around the V8 JavaScript engine. V8 includes its own [inspector and debugging client](https://nodejs.org/api/debugger.html) which you can start with the `inspect` argument (do not confuse this with the `--inspect` flag described in [Debugging Node.js Apps with Chrome](#debugging-nodejs-apps-with-chrome) below):

```sh
node inspect index.js
```

The debugger pauses at the first line and displays a `debug` prompt:

```sh
$ node inspect index.js
< Debugger listening on ws://127.0.0.1:9229/b9b6639c-bbca-4f1d-99f9-d81928c8167c
< For help, see: https://nodejs.org/en/docs/inspector
<
connecting to 127.0.0.1:9229 ... ok
< Debugger attached.
<
Break on start in index.js:4
  2
  3 const
> 4   port = (process.argv[2] || process.env.PORT || 3000),
  5   http = require('http');
  6
```

Enter `help` to view a list of commands. You can step through the application with:

* `cont` or `c`: continue execution
    
* `next` or `n`: run the next command
    
* `step` or `s`: step into a called function
    
* `out` or `o`: step out of a called function and return its caller
    
* `pause`: pause running code
    

You can also:

* watch variable values with [`watch('x')`](https://nodejs.org/api/debugger.html#debugger_watchers)
    
* set breakpoints with the [`setBreakpoint()`/`sb()` command](https://nodejs.org/api/debugger.html#debugger_breakpoints) (or you can insert a `debugger;` statement in your code)
    
* `restart` a script
    
* `.exit` the debugger (note the initial `.` period)
    

If this sounds clunky, *it is*. You should only attempt to use the built-in debugging client when there's no other option or you're feeling masochistic.

## Debug Node.js Apps with Chrome

Start the Node.js V8 inspector with the `--inspect` flag:

```sh
node --inspect index.js
```

(`nodemon` also supports the flag.)

It starts the debugger listening on `127.0.0.1:9229`:

```sh
Debugger listening on ws://127.0.0.1:9229/4b0c9bad-9a25-499e-94ff-87c90afda461
```

If you're running the Node.js application on another device or Docker container, ensure port `9229` is accessible and grant remote access using:

```sh
node --inspect=0.0.0.0:9229 index.js
```

You can use `--inspect-brk` instead of `--inspect` to halt processing on the first statement so you can step through line by line.

Open the Chrome web browser (or any other Chromium-based browser) and enter `chrome://inspect` in the address bar:

![Chrome inspect](https://i.imgur.com/kOcyds4.png align="left")

Your Node.js application should appear as a **Remote Target** after a few seconds. If it's not found, check **Discover network targets** and click the **Configure** button to add the IP address and port of the device running your application.

Click the target's **inspect** link to launch DevTools. This should be familiar if you've used the browser to debug client-side applications.

![Chrome DevTools](https://i.imgur.com/MZtwtFu.png align="left")

To load, edit, and save files directly from DevTools, open the **Sources** pane, click **\+ Add folder to workspace**, select the location of your Node.js files, and click **Agree**. You can now open any of your app's scripts from the left-hand pane or by pressing `Ctrl` | `Cmd` + `P` and entering the file name.

Click any line number to set a breakpoint (shown as a blue marker):

![Chrome source files](https://i.imgur.com/zAu25m6.png align="left")

A **breakpoint** specifies where the debugger should pause processing. This allows you to inspect the state of the program including local and global variables. You can define any number of breakpoints or add `debugger` statements to your code which also halt processing when the debugger is running.

![Chrome DevTools breakpoint](https://i.imgur.com/ntIb1lt.png align="left")

The right-hand panels provide:

* a **Watch** pane where you can monitor a variable by clicking the **+** icon and entering its name
    
* a **Breakpoints** pane where you can view, enable, or disable breakpoints
    
* a **Scope** pane where you can examine all variables
    
* a **Call Stack** pane where you can view the functions called to reach this point.
    

A row of icons appears above the **Paused on breakpoint** message.

![Chrome breakpoint icons](https://i.imgur.com/p8Koitd.png align="left")

From left to right, these icons perform the following actions:

1. **resume execution**: continue processing to the next breakpoint
    
2. **step over**: execute the next command but stay within the current function; don't jump into any function it calls
    
3. **step into**: execute the next command and jump into any function it calls
    
4. **step out**: continue processing to the end of the function and return to the calling command
    
5. **step**: similar to **step into**, except it won't jump into `async` functions
    
6. **deactivate all breakpoints**
    
7. **pause on exceptions**: halt processing whenever an error occurs.
    

### Set Conditional Breakpoints in Chrome

Imagine you have a loop which runs 1,000 iterations but you're interested in the state of the last one:

```javascript
for (let i = 0; i < 1000; i++) {
  // set breakpoint here?
}
```

Rather than clicking **resume** 999 times, you can right-click the line, choose **Add conditional breakpoint**, and enter a condition such as `i = 999`:

![Chrome conditional breakpoint](https://i.imgur.com/7ky8t7p.png align="left")

Conditional breakpoints appear in yellow rather than blue.

### Set Log Points in Chrome

Log points are `console.log()` without code! An expression is output when the code executes a line but, unlike a breakpoint, processing is not paused. To add a log point, right-click any line, choose **Add log point**, and enter an expression, e.g. `'loop counter i', i`

![Chrome log point](https://i.imgur.com/AYUqDct.png align="left")

## Debug Node.js Apps with VS Code

VS Code supports Node.js and has a built-in debugging client. No configuration when you run a Node.js application on your local system. Open your starting script (typically `index.js`), activate the **Run and Debug** pane, click the **Run and Debug Node.js** button, and choose the Node.js environment. Click any line to activate a breakpoint.

If you're running a web application, open it in any browser and VS Code halts execution when it encounters a breakpoint or `debugger` statement:

![VS Code debugger](https://i.imgur.com/GAoovfl.png align="left")

VS Code debugging is similar to Chrome DevTools with **Variables**, **Watch**, **Call stack**, and **Breakpoints** panes. The **Loaded Scripts** pane shows scripts loaded by your application, although this may include those internal to Node.js.

The toolbar of action icons allows you to:

1. **resume execution**: continue processing to the next breakpoint
    
2. **step over**: execute the next command but stay within the current function; don't jump into any function it calls
    
3. **step into**: execute the next command and jump into any function it calls
    
4. **step out**: continue processing to the end of the function and return to the calling command
    
5. **restart** the application and debugger
    
6. **stop** the application and debugger
    

Like Chrome DevTools, you can right-click any line to add:

* a standard breakpoint.
    
* a conditional breakpoint which halts the program under specified criteria, e.g. `x > 3`.
    
* a logpoint which evaluates expressions in curly braces, e.g. `URL: { req.url }`
    

For more information, refer to [Debugging in Visual Studio Code](https://code.visualstudio.com/docs/introvideos/debugging).

### VS Code Advanced Debugging Configuration

Further VS Code configuration may be necessary if you want to debug code on another device, a virtual machine, or need to use alternative launch options such as `nodemon`.

The editor stores launch configurations in a `launch.json` file inside a hidden `.vscode` folder in your project. To generate the file, click the **create a launch.json file** link at the top of the **Run and Debug** pane and choose the **Node.js** environment.

![VS Code launch configuration](https://i.imgur.com/7vvIENX.png align="left")

You can add any number of configuration setting objects to the `"configurations": []` array using the **Add Configuration** button. VS Code can either:

1. **Launch** a Node.js process itself, or
    
2. **Attach** to a debugging Web Socket server, perhaps running on a remote machine or Docker container.
    

The screenshot above shows a `nodemon` launch configuration. The **Add Configuration** button provides a `nodemon` option so you should edit the `"program"` property to point at your entry script (`${workspaceFolder}/index.js`).

Save `launch.json`, then select `nodemon` from the drop-down list at the top of the **Run and Debug** pane, and click the green run icon:

![VS Code launch](https://i.imgur.com/kQnfWzO.png align="center")

`nodemon` will start your application. You can edit the code and set breakpoints or logpoints as before.

For further information, refer to the [VS Code launch configurations](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations).

VS Code can debug any Node.js application, but the following extensions can make life easier:

* [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers): connect to apps running in Docker containers
    
* [Remote - SSH](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh): connect to apps running on a remote server
    
* [Remote - WSL](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl): connect to apps running on Linux in WSL on Windows
    

## Other Debug Options for Node.js

The [Node.js Debugging Guide](https://nodejs.org/en/docs/guides/debugging-getting-started/) provides advice for other IDEs and editors including Visual Studio, JetBrains, WebStorm, Gitpod, and Eclipse.

[ndb](https://github.com/GoogleChromeLabs/ndb) offers an improved debugging experience with powerful features such as attaching to child processes and script black-boxing to limit file access.

The [IBM report-toolkit for Node.js](https://github.com/ibm/report-toolkit) works by analyzing data output when `node` runs with an `--experimental-report` option.

Finally, commercial services such as [LogRocket](https://logrocket.com/) and [Sentry.io](http://Sentry.io) integrate with your live web application on both the client and the server to record errors as they're encountered by real users.

## Summary

JavaScript and Node.js debugging has become considerably easier over the past decade. Use whatever tool is practical to locate a problem. There's nothing wrong with `console.log()` for quick bug hunting, but Chrome DevTools or VS Code may be preferable for more complex problems. Learn how to use the tools and you'll create more robust code while spending less time fixing bugs.

Finally, debugging has its own set of obscure jargon to learn:

| Term | Explanation |
| --- | --- |
| **breakpoint** | a line at which a debugger halts a program so you can inspect the state |
| **breakpoint (conditional)** | a breakpoint triggered by a certain condition, such as a value reaching 100. Sometimes known as a **watchpoint** |
| **debugger** | a tool that offers debugging facilities |
| **duplication** | a known bug reported before |
| **feature** | as in *"it's not a bug, it's a feature"* quoted by all developers |
| **frequency** | how often a bug occurs |
| **it doesn't work** | the most-often made but least useful bug report |
| **logpoint** | a debugger instruction which shows the value of an expression during execution but does not halt the application |
| **logging** | output of runtime information to the console or a file |
| **logic error** | the program works but doesn't act as intended |
| **priority** | the importance of a bug fix |
| **race condition** | hard-to-trace bugs dependent on the sequence or timing of uncontrollable events |
| **refactoring** | rewriting code to aid readability and maintenance |
| **regression** | re-emergence of a fixed bug perhaps owing to other updates |
| **related** | a bug that's similar or related to another |
| **reproduce** | the steps required to reliably raise an error |
| **RTFM error** | user incompetence disguised as a bug report, typically followed by a developer's response that they should "Read The *Friendly* Manual" |
| **step into** | step into a called function when debugging |
| **step out** | complete execution of the current function and return to the calling code when debugging |
| **step over** | complete execution of a command without stepping into any functions it calls when debugging |
| **severity** | the impact of a bug on the system. Data loss is more problematic than a one-pixel UI alignment issue unless the frequency of occurrence is low |
| **stack trace** | the historical list of all functions called before the error occurred |
| **syntax error** | typographical errors, such as `consile.leg()` |
| **user error** | an error caused by a user input rather than the application (a fix may still be necessary) |
| **watch** | a variable or expression output while debugging |

*Happy debugging!*
