Often when an application is not working the way it is supposed to work, developers gravitate to using logging methods like console.log() to log variables in the console as they debug. While this works for small projects, it becomes tedious on larger projects and ends up cluttering the console. Instead of progressing with debugging, you end up spending most of the time trying to decipher the output. To remedy this, you can use a debugger, which is a tool that pauses the execution of a program allowing you to step through and execute your code one line at a time.

In Node.js, you can use the command-line debugger and the following are some reasons to use it:

  • Watching and inspecting variables.
  • Moving through the code step-by-step. You can also skip the code that you know may not be the source of the bug.
  • Setting multiple breakpoints in your code in places where you suspect bugs.
  • Accessing the REPL to quickly experiment with code.

In this tutorial, you will use the command-line debugger to debug a sample Node.js program. First, you will step through the code using the debugger. Then you will set breakpoints in your code to pause execution. After that, you will watch variables during debugging and finally debug with the debug statement.

Getting Started

Before we start debugging, we will create the project directory and create a sample program.

Open your terminal, then create a new directory and move into it:

  1. mkdir debug-node && cd debug-node

In your text editor, create a main.js file and add the following code:

debug-node/main.js
programStatus = "begin";
elements = ["div", "footer", "main"];
htmlTags = [];

for (let i = 0; i < elements.length; i++) {
  htmlTags.push(`<${elements[i]}>`);
}

function displayHtmlTags(tags) {
  for (let i = 0; i < tags.length; i++) {
    console.log(tags[i]);
  }
}

displayHtmlTags(htmlTags);
programStatus = "end";

The program takes an array of strings and converts each element into an HTML tag using a for loop. After that, the displayHtmlTags() function logs each tag in the console.

Run the file:

  1. node main.js

You will see output that looks similar to this:

Output
<div> <footer> <main>

Now that you've created the sample program, you'll start using the debugger next.

Stepping Through Your Code Using a Debugger

In this section, you will start using the debugger to step through your code.

You can start the debugger with the following command:

  1. node inspect main.js

Running the command will change your terminal prompt to look like the following:

< Debugger listening on ws://127.0.0.1:9229/60f3e3d4-4eb1-4b91-8b45-d455fa83cc50
<
< For help, see: https://nodejs.org/en/docs/inspector
<
 ok
< Debugger attached.
<
Break on start in main.js:1
> 1 programStatus = "begin";
  2 elements = ["div", "footer", "main"];
  3 htmlTags = [];
debug>

When the debugger starts, it pauses execution on the first line and outputs Break on start in main.js:1 and the first three lines of your source code. The > sign indicates the line where the execution has paused and the debug> prompt is where you enter the commands to interact with the debugger.

To execute your code with the debugger, you can use: next and step. If you want save a few keystrokes, you can use n for next and s for step.

Let's execute the code with the debugger using n, then pressing ENTER:

Break on start in main.js:1
> 1 programStatus = "begin";
  2 elements = ["div", "footer", "main"];
  3 htmlTags = [];
debug> n

The debugger will pause execution on line 2:

break in main.js:2
  1 programStatus = "begin";
> 2 elements = ["div", "footer", "main"];
  3 htmlTags = [];
  4
debug>

To execute all the subsequent lines, you can keep pressing n, then ENTER. As mentioned, you can also use s, so let's restart the debugger to use s:

  1. restart

The debugger will pause at line 1 again:

...
Break on start in main.js:1
> 1  = "begin";
  2 elements = ["div", "footer", "main"];
  3 htmlTags = [];
debug>

This time, use s to execute line 1:

Break on start in main.js:1
> 1 programStatus = "begin";
  2 elements = ["div", "footer", "main"];
  3 htmlTags = [];
debug> s

Upon entering s, the debugger will pause on line 2:

break in main.js:2
  1 programStatus = "begin";
> 2 elements = ["div", "footer", "main"];
  3 htmlTags = [];
  4
debug>

While step and next can be used to step through code, there is an important difference between them. step calls a function and pauses on the first line within the function while next executes a function without pausing inside the function.

To see this distinction, exit the debugger with CTRL + D or enter the following:

  1. .exit

Create a displayTags.js file and add the following:

debug-node/displayTags.js
htmlTags = ["<div>", "<footer>", "<main>"];

function displayHtmlTags(tags) {
  for (let i = 0; i < tags.length; i++) {
    console.log(tags[i]);
  }
}

displayHtmlTags(htmlTags);
console.log("finished");

In the preceding code, we invoke displayHtmlTags() to log each tag from the htmlTags array in the console.

Let's start the debugger with the displayTags.js file:

  1. node inspect displayTags.js

Keep stepping through the code with n till the debugger reaches line 10. The most interesting thing happens when line 9 executes:

...
break in displayTags.js:9
  7 }
  8
> 9 displayHtmlTags(htmlTags);
 10 console.log("finished");
 11
debug> n
< <div>
<
< <footer>
< <main>
<
break in displayTags.js:10
...

When the displayHtmlTags() function runs, the HTML tags are logged in the console and you don't step in the displayHtmlTags() function to execute each line one by one.

Now let's compare this with step. Restart the debugger:

  1. restart

Keeping going through the code with s till line 10 and observe what happens after line 9 executes:

debug> s
break in displayTags.js:9
  7 }
  8
> 9 displayHtmlTags(htmlTags);
 10 console.log("finished");
 11
debug> s
break in displayTags.js:4
  2
  3 function displayHtmlTags(tags) {
> 4   for (let i = 0; i < tags.length; i++) {
  5     console.log(tags[i]);
  6   }
debug>
...

As you can see in the debugger, the execution paused on line 4 inside the displayHtmlTags() function. From there, you can keep executing each line with s, but be careful, sometimes you may end up stepping through Node.js source code. When that happens, you can restart the debugger or exit.

With that, stop the debugger:

  1. .exit

Now, here is a tip on when to use step and next. If you are stepping through the code and know that certain functions in your code work fine, you can use n. If you come across a function that you suspect could be the source of a bug, you can use s to step through the function and then switch to n. Or simply put, if you need to step through a function, use s; otherwise, use n.

You can now step through code using next and step. In the next section, you will use breakpoints to pause execution at specific lines.

Setting Breakpoints

While you can step through code with next and step, it gets tiring fast. A large program typically has hundreds of lines and stepping through code from line 1 till you get the point you suspect a bug is tedious. This is why the Node.js debugger provides breakpoints. A breakpoint can pause the execution of code at any line you choose. You can specify that the debugger should pause at line 7 instead of 1, which can save you a lot of keystrokes.

To use breakpoints, start the debugger with the main.js file again:

  1. node inspect main.js

To set a breakpoint, you use the setBreakpoint() or sb() method. Let's use the sb() method to set a breakpoint on line 6:

  1. sb(6)

When you call sb(6) and press ENTER, the debugger will create a breakpoint on line 6:

debug> sb(6)
  1 programStatus = "begin";
  2 elements = ["div", "footer", "main"];
  3 htmlTags = [];
  4
  5 for (let i = 0; i < elements.length; i++) {
> 6   htmlTags.push(`<${elements[i]}>`);
  7 }
  8
  9 function displayHtmlTags(tags) {
 10   for (let i = 0; i < tags.length; i++) {
 11     console.log(tags[i]);
debug>

From here, you can use c or cont to tell the debugger to stop the execution at the next breakpoint(line 6) or finish running the program.

Note that the debugger is still paused on line 1 and calling sb(6) only created the breakpoint. Let's tell the debugger to resume execution and pause on the breakpoint on line 6:

  1. c

After that, you can use n to step through the loop until you are satisfied.

Assuming line 8 through 15 has no issues, call the sb() method with 16 to set a breakpoint on line 16:

  1. sb(16)

The debugger will indicate a breakpoint in the code at line 16 using >:

debug> sb(16)
 11     console.log(tags[i]);
 12   }
 13 }
 14
 15 displayHtmlTags(htmlTags);
>16 programStatus = "end";
 17
debug>

As a reminder, it doesn't mean that the debugger has paused on line 16. You have only set a breakpoint.

To understand how to work with breakpoints, let's restart the debug session:

  1. restart

You should be able to see "2 breakpoints restored", confirming that the breakpoints still exist:

2 breakpoints restored.
Break on start in main.js:1
> 1 programStatus = "begin";
  2 elements = ["div", "footer", "main"];
  3 htmlTags = [];

You can also see the breakpoints with the breakpoints command:

debug> breakpoints
#0 main.js:6
#1 main.js:16
debug>

As explained, the debugger pauses on line 1 for a new session. To pause on the breakpoint on line 6, enter c:

debug> c
break in main.js:6
  4
  5 for (let i = 0; i < elements.length; i++) {
> 6   htmlTags.push(`<${elements[i]}>`);
  7 }
  8
debug>

The debugger will pause on line 6 where you can use n or s to investigate the bug; that is, assuming the code has bugs.

Let's pause at the next breakpoint. To do that, enter c a couple of times until you're on line 16:

  1. c

Using c allows you to get out of the loop much faster than using n or s, and once you are out of the loop, the debugger will pause on line 16.

break in main.js:16
 14
 15 displayHtmlTags(htmlTags);
>16 programStatus = "end";
 17
debug>

To finish executing the program, you can enter c again, but we don't want to do that yet. For now, let's learn how to watch and inspect variables in the debugger.

Watching Variables

So far. we know how to step through code with the debugger with step, next, breakpoints, and cont. But we don't know how to see the values in variables, which is a crucial step during debugging. Node.js debugger has the watch() method, which shows you the value in a variable each time you step through the code using the debugger.

Let's restart the debug session:

  1. restart

Confirm that you still got two breakpoints on lines 6 and 16 with the breakpoints command.

debug> breakpoints
#0 main.js:6
#1 main.js:16

If you don't have them, you can revisit the last section to learn how to add breakpoints.

To pause execution on the first breakpoint on line 6, enter c:

  1. c

Execution will now pause on line 6:

debug> c
break in main.js:6
  4
  5 for (let i = 0; i < elements.length; i++) {
> 6   htmlTags.push(`<${elements[i]}>`);
  7 }
  8

Lets watch the htmlTags and i variables :

debug > watch("htmlTags");
debug > watch("i");

In the preceding code, you call the watch() method with a string value of the variables you want to watch.

Now press n to step through the code:

  1. n

The debugger will now show you the two variables we are watching and their respective values:

break in main.js:5
Watchers:
  0: htmlTags = [ '<div>' ]
  1: i = 0
 ...
debug>

If you continue stepping through the loop with either n or c, you will see the variables update during each iteration:

...
Watchers:
  0: htmlTags = [ '<div>', '<footer>' ]
  1: i = 2
  ...

To see all the variables you are watching, you can use the watchers command:

  1. watchers

When run, it will show you all the variables you are watching:

debug> watchers
  0: htmlTags = [ '<div>', '<footer>' ]
  1: i = 2
debug>

You can also check a variable value without watching it with the exec statement. Let's see the value in the elements variable:

  1. exec elements

The debugger will show you the array under the elements variable:

debug> exec elements
[ 'div', 'footer', 'main' ]

Furthermore, you can use the Node.js REPL to see and experiment with variables:

  1. repl

Once in the REPL, the prompt will look like the following to let you know you are in the REPL:

debug> repl
Press Ctrl+C to leave debug repl
>

In the REPL, you can experiment with the variables:

> i
2
> htmlTags
[ '<div>', '<footer>' ]
> i * 3
6
>

Exit the Node.js REPL with CTRL+C.

Now that we know how to create breakpoints and watch variables, Let's stop watching the variables and clear the breakpoints.

In the debugger, you can stop watching variables using the unwatch() method:

debug > unwatch("i");
debug > unwatch("htmlTags");

Verify that you are no longer watching the variables with the following command:

  1. watchers

If you don't see any variables, it means you are not watching variables anymore.

You can also clear breakpoints using the cb() or the clearBreakpoint() method:

debug > cb("main.js", 6);
debug > cb("main.js", 16);

The cb() method takes the filename as the first argument and the breakpoint line number as the second argument.

To verify that the breakpoints are removed, use the breakpoints statement:

  1. breakpoints

When entered, it will show:

Output
No breakpoints yet

You can now exit the debugger:

  1. .exit

Now that you can watch variables and unwatch variables, you will use the debugger statement next.

Using The debugger Statement

JavaScript provides a debugger statement that you can use to indicate in your source code where you want execution to pause. With the debugger statement in your code, you can start the debugger and it will pause at that point.

To use the debugger statement, open main.js and add the debugger statement on line 6:

debug-node/main.js
...
for (let i = 0; i < elements.length; i++) {
  debugger;   // add this line
  htmlTags.push(`<${elements[i]}>`);
}

...
programStatus = "end";

Next, start the debugger:

  1. node inspect main.js

When the debugger starts, it will pause on line 1 like before:

Break on start in main.js:1
> 1 programStatus = "begin";
  2 elements = ["div", "footer", "main"];
  3 htmlTags = [];

To make the debugger pause on the debugger statement, enter c:

debug> c
break in main.js:6
  4
  5 for (let i = 0; i < elements.length; i++) {
> 6   debugger;
  7   htmlTags.push(`<${elements[i]}>`);
  8 }
debug>

Onwards you can continue with n or c as you see fit, but we will stop from here. Exit the debugger with CTRL + D.

If you want the debugger to pause execution on the debugger statement when you start the debugger, you can use the following command:

  1. NODE_INSPECT_RESUME_ON_START=1 node inspect main.js

The prompt will now show that the debugger has paused on line 6:

break in main.js:6
  4
  5 for (let i = 0; i < elements.length; i++) {
> 6   debugger;
  7   htmlTags.push(`<${elements[i]}>`);
  8 }
debug>

You can continue using the commands we have learned far to continue debugging from line 6.

With that, you now know how to use the debugger statement to pause execution in your code.

Conclusion

In this tutorial, you created a sample program and stepped through it with a Node.js debugger. You then used breakpoints to specify lines you want to pause execution. After that, you watched variables and used the debugger statement to debug. To learn more about debugging, visit the Node.js documentation