in programming chrome node ~ read.
Debugging Node.js - a quick intro

Debugging Node.js - a quick intro

So how exactly do you debug this thing? In the terminal? By doing a bunch of console.log() every here and there until you figure it out? Or maybe you're using the --debug flag? Here are some answers that helped me along the way, posting them here both as a note to my future self and as some kind of informative material.

Note: Code you're about to see is extracted from the OpenTrials API, from a branch I'm working on now.

1. You might want to try the --debug flag

Just imagine you work on a code base, and you'd like to be dropped in a node console at the point specified by a breakpoint. Let me throw in an example:

function urlFor(models) {  
  const modelsArray = (Array.isArray(models)) ? models : [models];
  const hasModelsWithoutId = (modelsArray.find((m) => m.id === undefined) !== undefined);

  if (!hasModelsWithoutId) {
    const path = modelsArray.map((model) => `${model.tableName}/${model.id}`);
    return `${config.url}/v1/${path.join('/')}`;
  }

  return undefined;
}

Before jumping into actual testing, let's manually inspect everything goes well by putting a breakpoint before the return value:

// some code left out
    return `${config.url}/v1/${path.join('/')}`;
  }

  debugger;

  return undefined;
}

Now, run the code with node --debug index.js. When you reach the breakpoint, type repl and hit Enter. Here you go, now you're "in the code" and you can execute things in that very context having the breakpoint. Cool, right?

2. Debug in Chrome DevTools (even when running tests!)

But the really nice thing about debugging comes bundled in Chrome-based browsers. Just point your browser to chrome://flags/#enable-devtools-experiments and tick that highlighted box there. Then restart your browser.

Now that you restarted the browser, open Developer Tools, go to Settings -> Experiments and tick "Node debugging".

Next step is to adjust your command line to use the Chrome inspector. In my case, I wanted to run tests and start inspecting at a breakpoint in the actual code (not in the test). Following the above procedure, I got the debugger; statement in, then adjusted this package.json line:

"test": "node ./node_modules/.bin/istanbul cover _mocha -- --grep e2e --invert",

into this (add the --inspect flag):

"test": "node --inspect ./node_modules/.bin/istanbul cover _mocha -- --grep e2e --invert",

UPDATE

A slightly better way is to also add the --debug-brk flag (see the update at the end of the article):

"test": "node --inspect --debug-brk ./node_modules/.bin/istanbul cover _mocha -- --grep e2e --invert",

You can pretty much combine it everywhere. Just be careful to remove it from scripts if you don't use it anymore.

Now, every time I run my test suite with npm test, it starts by printing a URL to the command line, for example chrome-devtools://devtools/remote/serve_file/@60cd6e859b9f557d2312f5bf532f6aec5f284980/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229/a47e83be-4d43-4570-ad3e-7fb3045f1651

If you follow that URL, you'll end up in a tab with just the DevTools open, that looks like this:

As you might've guessed, you can:

  • switch to the "Console" tab and be dropped in a REPL console allowing you to run commands in the context of the breakpoint
  • stay in the default view, which is a split screen containing source files on top and the JS console on the bottom
  • make additional inspections using the right hand controls, just like you would do with a "regular" JS app that runs in the browser.

Some samples of what you can do:

Autocomplete on properties and methods:

Inspect properties, run commands affecting the running code:

Alter already defined methods, overload stuff, you name it:

Enjoy!

UPDATE 2017-03-30

Turns out the default --inspect behaviour is pretty annoying. On my machine (currently running openSUSE Tumbleweed with KDE/i3wm) I couldn't make Chromium open the chrome-devtools://.* URLs on click. So I had to right click on the link, copy it, paste it in the browser location bar. But this took a bit over 2-3 seconds, and unfortunately this is about the amount node leaves you to connect to the debugging process. Fail to do so, and code starts running normally, sprints over your breakpoints and leaves you dry.

Thankfully, we have StackOverflow. I just found this post today, together with the fix:

  • add also the --debug-brk flag to the command
    • this will stop the execution on first occasion, generally on first LOC that gets executed
  • when you run it, take your time to open the browser / devtools, since the execution is now paused (oooo, superpowers!)
    • if all goes well, you'll have a message in the console output informing you the debugger is now connected
  • now look for a pause/play symbol around the upper right corner of the UI; that tells node to resume execution, which actually gives you a nice clean run from the beginning. Which I pretty much always missed without this trick
  • enjoy your breakpoints, never miss your first one, see your app starting up and all that

You're welcome. It saved me a fuckload of time and frustration, hope you'll find it useful too ;-)