Let's debug a node.js app using the built-in debugger!

Siddharth - Mar 3 '22 - - Dev Community

It's about time we stop using console.log to debug things. While console.* are really powerful tools, it's a pain to maintain.

Most of us can use our IDEs to debug code. But did you know that Node.js comes with a built-in debugger? It's a simple terminal-based one, but extremely powerful, too. Let's check it out by debugging a sample app using the built-in debugger

The code

This is the simple program we are going to be debugging right now:

const numbers = process.argv;
const sum = (a, b) => a + b;
let out = numbers.reduce(sum);
console.log(out);
Enter fullscreen mode Exit fullscreen mode

If you are a seasoned developer, you should have caught the bugs already. But forget it.

Here's some sample output from this program:

$ node index.js 1 2 3 4
/usr/local/Cellar/node/17.5.0/bin/node/Users/mac/Blog/Code/node-debug/index.js1234
Enter fullscreen mode Exit fullscreen mode

Firing up the debugger.

Node.js has two ways to start the debugger.

The first way is to run node --inspect file.js. This starts a WebSocket connection to the debugger and clients can use this connection to debug the code.

The second way is to use the built-in inspector client, using node inspect file.js (note that it's a command now, not a --flag)

We can start the inspector for our app by running node inspect index.js

$ node inspect index.js
< Debugger listening on ws://127.0.0.1:9229/516c8247-4fe0-471d-9c71-a87aa191b256
< For help, see: https://nodejs.org/en/docs/inspector
<
< Debugger attached.
<
 ok
Break on start in index.js:1
> 1 const numbers = process.argv;
  2 const sum = (a, b) => a + b;
  3 let out = numbers.reduce(sum);
debug>
Enter fullscreen mode Exit fullscreen mode

This gives us a debug prompt, where we can run commands to debug the code.

The basic ones are:

  • cont, c: Continue execution
  • next, n: Step next
  • step, s: Step in
  • out, o: Step out
  • repl: Enter a REPL in the current scope.

For now, let's press n<enter> to go to the next line.
The debugger will look like this now:

break in index.js:2
  1 const numbers = process.argv;
> 2 const sum = (a, b) => a + b;
  3 let out = numbers.reduce(sum);
  4 console.log(out);
debug>
Enter fullscreen mode Exit fullscreen mode

We are at the second line now.

Now, we know that there is something wrong with the numbers array. We can take a look at its value by "watching" it. We can run the command watch('numbers') to do so. Then, we can use the watchers command to print all watched variables.

This is what it will look like now:

debug> watch('numbers')
debug> watchers
  0: numbers =
    [ '/usr/local/Cellar/node/17.5.0/bin/node',
      '/Users/mac/Blog/Code/node-debug/index.js' ]
debug>
Enter fullscreen mode Exit fullscreen mode

We just spotted the bug! We forgot to get rid of the first 2 arguments in the process.argv 🤦. Silly me!

Let's fix the code. First, exit the debugger with Ctrl-d, then modify the first line:

const numbers = process.argv.slice(2);;
const sum = (a, b) => a + b;
let out = numbers.reduce(sum);
console.log(out);
Enter fullscreen mode Exit fullscreen mode

...but it's still broken! node index.js 1 2 3 4 just prints 1234.

Let's try debugging again. Fire up the debugger again and head to the third line, where the addition goes on.

$ node inspect index.js 1 2 3 4 # notice how we can pass arguments
< Debugger listening on ws://127.0.0.1:9229/0db8e855-c117-4511-a022-ab5c908cff46
< For help, see: https://nodejs.org/en/docs/inspector
<
< Debugger attached.
<
 ok
Break on start in index.js:1
> 1 const numbers = process.argv.slice(2);
  2 const sum = (a, b) => a + b;
  3 let out = numbers.reduce(sum);
debug> n
break in index.js:2
  1 const numbers = process.argv.slice(2);
> 2 const sum = (a, b) => a + b;
  3 let out = numbers.reduce(sum);
  4 console.log(out);
debug> n
break in index.js:3
  1 const numbers = process.argv.slice(2);
  2 const sum = (a, b) => a + b;
> 3 let out = numbers.reduce(sum);
  4 console.log(out);
  5
debug>
Enter fullscreen mode Exit fullscreen mode

Now, instead of going to the next line, we can step into the line so we know what exactly is going on.

Pressing s will move us back to the sum call. Now, we can watch a and b.

debug> step
break in index.js:2
  1 const numbers = process.argv.slice(2);
> 2 const sum = (a, b) => a + b;
  3 let out = numbers.reduce(sum);
  4 console.log(out);
debug> watch('a')
debug> watch('b')
debug> watchers
  0: a = '1'
  1: b = '2'
debug>
Enter fullscreen mode Exit fullscreen mode

We just spotted the second bug! We are trying to add two strings of numbers, instead of strings. Let's fix that right away by converting all the strings to numbers:

const numbers = process.argv.slice(2).map(Number);
const sum = (a, b) => a + b;
let out = numbers.reduce(sum);
console.log(out);
Enter fullscreen mode Exit fullscreen mode

Running this program node index.js 1 2 3 4 will now correctly output 10. Yay!

Wrap!

The Node.js built-in debugger can be a life-saver when you don't have a proper IDE at hand. You should take a moment to check out the official documentation for the debugger so you can understand the rest of the features. You could even build your own client!

What's your favorite debugging tool? 👀

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .