Interactive Debugging With Node.js
This post will introduce the Node.js debugging tools in Visual Studio Code (VS Code) for programmers who have never used a debugger before.
Join the DZone community and get the full member experience.
Join For FreeA “step-through debugger” (also called an “interactive debugger” or just “debugger”) is a powerful tool that can be very handy when your application isn’t behaving the way you expect. You can use it to pause your application’s code execution to:
- Inspect or alter application state.
- See the code execution path (“call stack”) that leads to the currently-executing line of code.
- Inspect the application state at earlier points on the code execution path.
Interactive debuggers* are familiar to every Java developer (among others), but they are less well known in the JavaScript world. This is unfortunate, both because debuggers can help diagnose logic issues, and because the JavaScript debugging tools today are the best and easiest to use they’ve ever been! This post will introduce the Node.js debugging tools in Visual Studio Code (VS Code) for programmers who have never used a debugger before.
Why Use a Debugger?
Debuggers are useful when writing your own code, but they really show their value when you’re working with an unfamiliar codebase. Being able to step through the code execution line by line and function by function can save hours of poring over source code, trying to step through it in your head.
The ability to change the value of a variable at runtime enables you to play through different scenarios without hard coding values or restarting your application. Conditional breakpoints let you halt execution upon encountering an error to figure out how you got there. Even if using a debugger isn’t part of your everyday process, knowing how they work adds a powerful tool to your toolbox!
Setting Up
You’ll use VS Code, which has a built-in Node.js debugger. To run code in the context of the VS Code debugger, you must first make VS Code aware of our Node.js project. For this demo you’ll create a simple Express app with express-generator
, as follows:
$ npm install express-generator -g
$ express test-app # create an application
$ cd test-app
$ npm install
$ code . # start VS Code
With VS Code open, open the Debug view by clicking the bug icon in the left sidebar menu. With the Debug view open, the gear icon at the top of the pane will have a red dot over it. This is because there are currently no “launch configurations:” configuration objects that tell VS Code how to run your application. Click the gear icon, select “Node.js,” and VS Code will generate a boilerplate launch configuration for you.
There are two ways to attach the VS Code debugger to your application:
- Set up a launch configuration and launch your app from within VS Code
- Start your app from the console with
node --debug-brk your-app.js
and run the “Attach” launch configuration
The first approach normally requires some setup beyond the scope of this post; read about it here. Because the package.json
has a run
script, VS Code automagically created a launch configuration based on that script. That means you can simply click the green “run” button to start our application in the debugger.
If all went well, you will see a new toolbar at the top of your screen with pause and play buttons (among others). The Debug Console at the bottom of the screen will tell you the command it ran and its output–something like this:
node --debug-brk=18764 --nolazy bin/www Debugger listening on port 18764
When you load http://localhost:3000 in a browser, you will see the Express log messages in this same Debug Console:
GET / 304 327.916 ms - - GET /stylesheets/style.css 304 1.313 ms - -
Now that you have the code running with the VS Code debugger attached, let’s set some breakpoints and start stepping through our code….
Breakpoints
A breakpoint is a marker on a line of code that tells the debugger “pause execution here.” To set a breakpoint in VS Code, click the gutter just to the left of the line number. For example, open routes/index.js
and set a breakpoint in the root request listener:
The breakpoints pane at the bottom left has a listing for the new breakpoint (along with entries for exceptions, which we’ll talk about momentarily). Now, when you hit http://localhost:3000 in a browser again, VS Code will pause at this point so you can examine what’s going on at that point:
With the code paused here, you can examine variables and their values in the variables pane and see how you got to this point in the code in the call stack pane. You may also notice that the browser has not loaded the page– that’s because it’s still waiting for the server to respond! We’ll take a look at each of the sidebar panes in turn, but for now, press the play button to continue code execution.
Now the server will send the finished page to the browser. When code execution resumes, the “play” button is no longer enabled.
Other Types of Breakpoints
In addition to breaking on a certain line each time it’s executed, you can add dynamic breakpoints that pause execution only in certain circumstances. Here are a few of the more useful ones:
- Conditional Breakpoint: After setting a breakpoint, right click on it and select “edit breakpoint,” then enter an expression to conditionally activate a breakpoint. For example, to activate a breakpoint only if the user is an admin, you might add
user.role === "admin"
to your conditional breakpoint. - Uncaught Exception: This is enabled by default. With this enabled, you don’t have to set any breakpoints to locate errors, the debugger will pause on any (uncaught) exceptions.
- All Exceptions: If you have robust error handling in your application, but you still want to see where errors come from before they’re caught and handled, enable this setting. Be warned, however, that many libraries throw and catch errors internally in the normal course of their execution, so this can be pretty noisy.
Variable Pane
In this pane, you can examine and change variables in the running application. Let’s edit our homepage route in routes/index.js
to make the title a variable:
/* GET home page. */
var ourTitle = 'Express';
router.get('/', function(req, res, next) {
res.render('index', { title: ourTitle });
});
After editing the code, restart the debugger so it picks up the new code. Do this by clicking the green circle/arrow button in the top toolbar. After editing a file with a breakpoint already set and restarting the debugger (as you just did), you’ll also want to check that your breakpoints are still in the right spot. VS Code does a pretty good job of keeping the breakpoint on the line you expect but it’s not perfect.
With our breakpoint on what’s now line 7 and with the debugger restarted, refresh your browser. The debugger should stop on line seven. You don’t see ourTitle
in the variable pane right away, because it’s not “Local” to that function, but expand the “Closure” section just below the “Local” section and there it is!
Double-clicking ourTitle
in the Variables Pane allows you to edit it. This is a great way to tinker with your application and see what happens if you switch a flag from true to false, change a user’s role, or do something else– all without having to alter the actual application code or restart your application!
The variable pane is also a great way to poke around and see what’s available in objects created by libraries or other code. For example, under “Local” you can see the req
object, see that its type is IncomingMessage
, and by expanding it you can see the originalUrl
, headers
, and various other properties and methods.
Stepping
Sometimes, rather than just pausing the application, examining or altering a value, and setting it running again, you want to see what’s happening in your code line by line: what function is calling which and how that’s changing the application state. This is where the “Debug Actions” toolbar comes in: it’s the bar at the top of the screen with the playback buttons. We’ve used the continue (green arrow) and restart (green circle arrow) buttons so far, and you can hover over the others to see the names and associated keyboard shortcuts for each. The buttons are, from left to right:
- Continue/Pause: Resume execution (when paused) or pause execution.
- Step over: Execute the current line and move to the next line. Use this button to step through a file line by line.
- Step in: When paused on a function call, you can use this button to step into that function. This can get a bit confusing if there are multiple function calls on one line, so just play around with it.
- Step out: Run the current function to its
return
statement & step out to the line of code that invoked that function. - Restart: Stop your debugging session (kill your application) and start it again from the beginning. Use this after altering code.
- Stop: Kill your application.
Watch Expressions
While stepping through your code, you may want to see the values of certain things. A “watch expression” will run (in the current scope!) at each paused/stopped position in your code and display the expression’s value. Hover over the Watch Expression pane and click the plus to add an expression. To see the user agent header of each request as well as ourTitle
, whether the response object has had headers sent, and the value of 1 + 1
, just for good measure, so add the following watch expressions:
req.headers['user-agent']
ourTitle
res._headerSent
1 + 1
When you refresh the browser the debugger pauses once again at the breakpoint on line 7, you can see the result of each expression:
Call Stack
The Call Stack Pane shows the function calls that got you to the current position in the code when execution is paused and enables you to step back up that stack and examine the application state in earlier “frames.” By clicking the frame below the current frame you can jump to the code that called the current function. In our case, the current frame is labeled (anonymous function)
in index.js [7]
, and the one before that the handle
function in layer.js
, which is a component of the Express framework:
Note that the request handling function is unnamed, hence “(anonymous function)
.” “Anonymous function?!” What’s that? Who knows! Moral: always name your functions!
Stepping down into the Express framework is not something I do every day, but when you absolutely need to understand how you got to where you are, the Call Stack Pane is very useful!
One especially interesting use of the Call Stack Pane is to examine variables at earlier points in your code’s execution. By clicking up through the stack, you can see what variables those earlier functions had in their scope, as well as see the state of any global variables at that point in execution.
All This and More…
There are many more features of the interactive debugger than I went over here, but this is enough to get you started. To learn more, take a look at the excellent documentation from Microsoft on the VS Code Debugger and using it with Node.js. Oh, and I should probably mention that all the debugging features outlined here (and more) are built-in to Firefox as well as Chrome, should you wish to use them on browser-based code. Happy Debugging!
* There’s no specific term I’ve found for this common collection of application debugging tools so I’m using the term “interactive debugging” in this article.
Published at DZone with permission of Sequoia McDowell. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments