Debug a Node App Inside of Visual Studio Code

Visual Studio code comes with many great features. Debugging JS inside of your editor can be very helpful.

Visual Studio Code is an open source Code Editor headed by Microsoft that borrows a lot from Visual Studio.

Visual Studio Code Home Page

It has been in development for a while, and has really matured as an editor that many developers are switching to it.

It comes with a couple of feature, to mention a few

  • Git/Github integration
  • Awesome Intellisense
  • Awesome set of plugins
  • Awesome debugging tools

We'll focus on Visual Studio Code's debugging ability for nodejs applications in this artilce.

DEBUGGING TOOLS

To see debugging in action, we'll first debug a small piece of javascript, just to get familiarized with the Visual Studio Code debugging tools.

Create an isolated directory, say vscode_debug, then inside it create a new file index.js

vscode_debug/index.js

let program = {
  name: 'vscode',
  language: 'typescript'
}

let otherProperties = {
  sourceCode: 'https://github.com/Microsoft/vscode',
  license:  'MIT'
}

// Add properties to targetObjects
function combine(targetObject, otherObject) {
  Object.assign(targetObject, otherObject)
}

combine(program, otherProperties)

We'll look at the effect of Object.assign on a given object.

We'll add a breakpoint at the line with combine(program, otherProperties), by clicking just before the line number.

Then, on the left pane, click on the debug icon as annotated above. The view below should be shown.

We have 4 sections of the left pane, which are populated during debug mode in VS Code.

  • variables - this is where all the variables in our debugging context and ther values will be displayed. We'll look at this in detail shortly.
  • Watch - This will enable us to list variables, or exressions and watch them change state as we debug our code.
  • Call Stack - This is simply a stack of the functions called when the app is running up to the point where we want to start our debugging.
  • Break Points - This lists all the breakpoints we put in our applications. In this case for instance, it has selected the index.js file and pointed to line 15.

To configure VS Code debugging, we need to create a launch.json file that VS Code uses for debugging. In the screenshot above, I've annotated a gear-like button at the top, when you click on it, a pop up will show. Select Node.js

A file launch.json is created. The important property to look at in the created file is the first child of configurations, and the property is program. Make sure the file written there is the one that we created. In our case index.js

vscode_debug/.vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      ...
      "program": "${workspaceRoot}/index.js",
      ...
      ...
  ]
}

That's all the set up we need. Now click on the run button.

When we click on the run button, a few things change

At the top of the Visual Studio code IDE, a set of buttons appear.

The first button with a run icon is called continue and is used to resume exectution until the next breakpoint, in case you have more than one breakpoint.

The next button, with a bent forward arrow, is called step over and is used to go to the next line in execution. In case it gets to a function like it is right now, it will not take the debugger into the function as expected, but will evaluate the function, return the result and continue.

If we need to get into a function call, we use the third button pointing down, with a dot. It's called the step into button. This is what we'll use next.

The final button, upward pointing arrow, step out is used to get out of function calls, or other nested scope that the debugger may be in.

If you look on the left pane, under variables, all the variables available in our execution context are listed there. If you've worked with nodejs before, __dirname, __filename, module and exports may be familiar, you can see their values here.

Our function, combine is also listed there as a funtion. Other local variables, program and otherProperties are laso listed there. You can expand them to see their values.

The callstack shows the function calls made up to when the breakpoint is reached. You can see the top most one is an anonymous function, immediately we get into the function, it should check the name and update it.

The watch is pretty interesting. While we can examine the values changing under variables, hover over the title of watch and click on the plus button. Then type in program. The program variable and its value is listed there. The cool thing with watch is that you could put in expressions too.

Now let's click on the step into button (arrow pointing directly downwards).

When you click first, the call stack will identify the name of the combine function and update. We then click again to get into the function, then one final time to exit the function. Notice the value of program in the watch list has changed. This is also true for the value under the variables section.

Debugging an Express App

Now that we have a basic understanding of how debugging works in Visual Studio Code, let's do a real word thing. We will work with an express app.

Setup

Install the express generator, if you don't already have it.

$ npm install express-generator -g

Next generate an express app. We'll call it actors. All it will do is return actors stage names, and make a call to the github api, to see if there's a repo with the stage names.

$ express actors
$ cd actors
$ npm install
$ npm start

This should straight forward for an express app. It will start an express app and opening http://localhost:3000 should show express app runnning in the browser.

We'll add another route in the users route to return the actors.

routes/users.js

var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

router.get('/:id', function(req, res) {
  // List of actors
  const actors = [
    {
      id: 1,
      name: 'Heisenberg'
    },
    {
      id: 2,
      name: 'Pinkman'
    },
    {
      id: 3,
      name: 'Gus'
    }
  ]

  // Get actor based on passed id
  const actor = actors.filter(actor => actor.id == req.params.id)

  // Send back actor as response
  res.send(actor)
})

module.exports = router;

If you run npm start, and go to your browser http://localhost:3000/users/1 (change id), you should see the respective actors.

Debugging a possible error

Suppose you pass in http://localhost:3000/users/ganga, or even an id greater that 3, you'll get an empty array.

Even though this seems pretty obvious, let's examine it. Click on the debug icon on the left pane, then, on the gear-like icon, then choose node.js to create a Launch.json file

It should detect that you are running an express app from the package.json, so the program paramter should be "program": "${workspaceRoot}/bin/www",.

Next set up a breakpoint in routes/users.js just where we define the actor variable.

Next click on the run button, and then open http://localhost:3000/users/ganga in the browser, then come back to Visual Studio Code. Under the variables, the actor is undefined. I've expanded the req variable, and its params property. We can see the value of id is what we passed in the url.

If you've ever wondered what an express request, or response entails, this is your chance to get digging.

Click on step over, the bent arrow button, we can see that the actor is an empty array.

You can click on the continue button to stop debugging.

While this example is very simple, we see that debugging works across even the imported/required files in a node environment, since in this case, the users route is required in the app.js file.

Asyncronous calls

We'll add one last route, to see the effect on asyncronous calls.

Let's add axios to make calls to other apis.

$ npm install --save axios

Next we'll modify api, to look for actors github profiles.

routes/users.js

var express = require('express');
var router = express.Router();
var axios = require('axios')

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

router.get('/:id', function(req, res) {
  const actors = [
    {
      id: 1,
      name: 'Heisenberg'
    },
    {
      id: 2,
      name: 'Pinkman'
    },
    {
      id: 3,
      name: 'Gus'
    }
  ]

  const actor = actors.filter(actor => actor.id == req.params.id)

  // Get actor github profiles asyncronously from github
  axios.get('https://api.github.com/users/'  + actor[0].name)
    .then(githubProfile => {
      res.send(githubProfile)
    })
    .catch(error => {
      res.send(error)
    })
})
module.exports = router;

We've simply added a request call to the github api to get an actors github profile. Stop the debugger if it was running and run npm start, then head over to http://localhost:3000/users/1.

¯\(ツ)/¯, It's an empty object. Let's see what's going on.

Adding a breakpoint to the response of the axios request, and clicking the run button next to debug, then visiting http://localhost:3000/users/1, we can clearly see the mistake we made. This githubProfile is a response, and it has a data property. Our bug is that, we needed to pass in the data property to the response. So, the simple fix is res.send(githubProfile.data).

Visiting http://localhost:/3000/users/1, should display the user details.

Conclusion

Visual Studio Code debugging for NodeJS can go way deeper than what we've discussed here. It also comes with support for plugins, such as Chrome Debugger, which enhance it's debugging features.

Ganga Chris

I'm a Full Stack Developer (Mostly JavaScript, PHP & Go).