Debugging Node.js Applications with Chrome Devtools

In this node.js tutorial we will learn how we can debug our node.js applications with Chrome Devtools. These Devtools are really handy and provides a robust suite of tools when debugging simple to complex node apps.

Let’s Start

We’re going to debug a small web server, so let’s create that real quick.

On the command line, we need to run the following commands. The commands below will create a new folder “debugging-node-app”, setup express.js and create three different files.

$ mkdir debugging-node-app
$ cd debugging-node-app
$ npm init -y
$ npm install --save express
$ touch index.js future.js past.js

Our app.js file should contain the following:

const express = require('express') 
const app = express() 
const past = require('./past') 
const future = require('./future') 
app.get('/:age', (req, res) => { 
  res.send(past(req.params.age, 10) + future(req.params.future, 10)) 

Our past.js file should look like this:

module.exports = (age, gap) => { 
  return `${gap} years ago you were ${Number(age) - gap}<br>` 

And our future.js file should be as follows:

module.exports = (age, gap) => {
   return `In ${gap} years you will be ${Number(age) + gap}<br>`

When we run our server, and navigate our browser to http://localhost:3000/31 , the output is as follows:

10 years ago you were 21 
In 10 years you will be NaN 

It looks like we have a Not a Number problem.

Let’s start our server in inspection mode:

$ node --inspect app.js

This will output a message that the debugger is listening.

We can connect to the debugger using the Chrome browser. Let’s open Chrome and navigate to chrome://inspect.

Ensuring that we’re in the Devices tab, we should be able to see our Node process underneath the Remote Target section, as in the following screenshot:

We should then see something like the following:

Notice that the Devtools Code section shows an additional outer function wrapping the code we placed into app.js. This outer function is added at runtime to each code file loaded into the process (either by directly starting the file with node or by using require to load it). This outer function is the module wrapper, it’s the mechanism Node uses to supply local references like module and __filename that are unique to our module without polluting global scope.

Now let’s set a breakpoint inside our route handler, on line 7.

If we click the number 7 in the LOC column to the left of the code, an arrow shape will appear over and around the number (which will turn white). Over in the right-hand column, in the Breakpoints pane we should also see a checkbox with app.js:7 next to it, while beneath that is the code from the line we’ve set a breakpoint on.

In short, the Devtools GUI should now look something like the following:

Now let’s open a new tab and navigate to http://localhost:3000/31:

This will cause the breakpoint to trigger, and Devtools will immediately grab focus.

The next thing we see should look like the following:

We can see line 7 is now highlighted, and there’s a sort of tooltip showing us the values of the req and res objects on the line above.

Over in the right column, the Call Stack panel is full of call frames (the functions in the stack), and there’s now a blue play button in the control icons at the top of the right column. If we were to scroll the right column, we’d also see the Scope pane is populated with references.

The debugger is waiting for us to allow execution to proceed, and we can chose whether to step over, in, or out of the next instruction.

Let’s try stepping in. This is the down arrow pointing to a dot, the third icon from the left in the controls section:

When we press this, we step into the past function, which is in the past.js file, so Devtools will open a new tab in the center code panel and highlight the line that is about to execute (in our case, line 2):

So let’s step out of the past function by pressing the arrow pointing up and away from a dot, next to the step-in icon:

The second line of the output seems to have the issue, which is our future function.

Now that we’ve stepped out, we can see that the call to future is highlighted.

Now let’s press the step-in icon again, which will take us into the future function in the future.js file:

Okay, this is the function that generates that particular sentence with the NaN in it. A NaN can be generated for all sort of reasons, such as dividing zero by itself, subtracting Infinity from Infinity, trying to coerce a string to a number when the string does not hold a valid number, to name a few. At any rate, it’s probably something to do with the values in our future function.

Let’s hover over the gap variable. We should see the following:

Seems fine. Now let’s hover over the age variable:

Wait… why does that say undefined ? We vicariously passed 31 by navigating to http://localhost:3000/31.

To be sure our eyes aren’t deceiving us, we can double-check by collapsing the Call Stack section (by clicking the small downwards arrow next to the C of Call Stack). This will make room for us to easily see the Scope section, which reports that the age variable is indeed undefined, as in the following screenshot:

Well, Number(undefined) is NaN, and NaN + 10 is also NaN.

Why is age set to undefined?

Let’s open up the Call Stack bar again and click the second row from the top (which says app.get).

We should be back in the index.js file again (but still frozen on line 2 of future.js), like so:

Now let’s hover over the value we’re passing in to future:

That’s undefined too. Why is it undefined?

Oh. That should be req.params.age, not req.params.future. Oops.

To be absolutely sure, let’s fix it while the server is running. If we hit the blue play button once we should see something like this:

Now let’s click line 7 again to remove the breakpoint. We should be seeing:

Now if we click immediately after the e in req.params.future we should get a blink cursor. We backspace out the word future and type the word age, making our code look like this:

Finally, we can live save those changes in our running server by pressing Cmd + S on macOS, or Ctrl + S on Windows and Linux.

Finally, let’s check our route again:

OK, we’ve definitely found the problem, and verified a solution.

Muhammad Mubeen

Muhammad Mubeen

Mubeen is a full-stack web & mobile app developer who is very proficient in MEAN.js, Vue, Python, Ionic 4, Flutter, Firebase, ROR, and PHP. He has created multiple mobile and web applications. He is very passionate about sharing his knowledge.

Leave a Reply

Your email address will not be published. Required fields are marked *