debug
Node.js offers multiple debugging approaches. This page covers everything from quick console.log debugging to graphical debuggers using the --inspect flag with Chrome DevTools or VSCode, as well as the debug module for conditional log output.
Debugging Method Comparison
| Method | Description | Best For |
|---|---|---|
| console.log | Outputs values to the terminal. The simplest approach. | Quick value checks, tracing execution flow. |
| node --inspect | Connects to Chrome DevTools for breakpoint-based debugging. | Variable monitoring, call stack inspection. |
| VSCode Debugger | Set breakpoints in the editor and step through code. | Full-scale development, code step execution. |
| debug module | Outputs logs conditionally per namespace. Safe design that does not leak into production. | Library and module-specific debug logs. |
| console.trace() | Outputs the call stack trace of the caller. | Finding out which code called a function. |
| console.time() | Measures execution time. | Quick performance measurement. |
console.log Debugging
console.log() is the foundation of debugging. console.error() writes to stderr, allowing logs and errors to be handled separately. console.dir() expands and displays even deeply nested objects.
console_debug.js
// Basic output
var fighter = { name: 'Son Goku', power: 'Kamehameha', level: 9000 };
console.log('Fighter data:', fighter);
// console.error writes to stderr
console.error('[ERROR] Damage calculation failed');
// console.dir shows object details
var nested = { team: { name: 'Earthlings', members: ['Son Goku', 'Vegeta', 'Piccolo'] } };
console.dir(nested, { depth: null }); // depth: null expands all levels
// console.table displays arrays and objects in table format
var fighters = [
{ name: 'Son Goku', power: 'Kamehameha', level: 9000 },
{ name: 'Vegeta', power: 'Galick Gun', level: 8000 },
{ name: 'Frieza', power: 'Death Beam', level: 10000 },
];
console.table(fighters);
// Measure execution time
console.time('battle time');
var sum = 0;
for (var i = 0; i < 1000000; i++) {
sum += i;
}
console.timeEnd('battle time'); // battle time: X.XXXms
// Output a stack trace
function transform() {
console.trace('Stack trace');
}
transform();
node console_debug.js
Fighter data: { name: 'Son Goku', power: 'Kamehameha', level: 9000 }
[ERROR] Damage calculation failed
{ team: { name: 'Earthlings', members: [ 'Son Goku', 'Vegeta', 'Piccolo' ] } }
┌─────────┬───────────┬──────────────┬───────┐
│ (index) │ name │ power │ level │
├─────────┼───────────┼──────────────┼───────┤
│ 0 │ 'Son Goku'│'Kamehameha' │ 9000 │
│ 1 │ 'Vegeta' │'Galick Gun' │ 8000 │
│ 2 │ 'Frieza' │'Death Beam' │ 10000 │
└─────────┴───────────┴──────────────┴───────┘
battle time: 2.541ms
Trace: Stack trace
at transform (console_debug.js:27:13)
at Object.<anonymous> (console_debug.js:30:1)
node --inspect and Chrome DevTools
Launching with the node --inspect flag lets you connect Chrome DevTools to the Node.js process, enabling breakpoint stops, variable monitoring, and call stack inspection.
inspect_sample.js
// Script to debug
var fighters = [
{ name: 'Son Goku', power: 9000 },
{ name: 'Vegeta', power: 8000 },
{ name: 'Frieza', power: 10000 },
{ name: 'Piccolo', power: 3500 },
];
function findStrongest(list) {
var strongest = list[0];
for (var i = 1; i < list.length; i++) {
// Set a breakpoint here to watch how strongest changes
if (list[i].power > strongest.power) {
strongest = list[i];
}
}
return strongest;
}
var result = findStrongest(fighters);
console.log('Strongest:', result.name, 'Power:', result.power);
Follow these steps to debug.
node --inspect inspect_sample.js Debugger listening on ws://127.0.0.1:9229/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx For help, see: https://nodejs.org/en/docs/inspector
Enter chrome://inspect in the Chrome address bar. The Node.js process appears under "Remote Target". Click "inspect" to open DevTools, where you can set breakpoints in the "Sources" tab.
To stop immediately when the script starts, use --inspect-brk.
node --inspect-brk inspect_sample.js Debugger listening on ws://127.0.0.1:9229/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
VSCode Debugger Configuration
VSCode has a built-in Node.js debugger. Create .vscode/launch.json to configure it. Click the gutter (left of line numbers) to set breakpoints, and press F5 to start debugging.
.vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Run",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/inspect_sample.js"
},
{
"type": "node",
"request": "attach",
"name": "Attach to Running Process",
"port": 9229
}
]
}
The operations after configuration are as follows.
| Key | Description |
|---|---|
| F5 | Start the debug session. |
| F9 | Toggle a breakpoint on the current line. |
| F10 | Step over (advance to next line without entering the function). |
| F11 | Step into (enter the function). |
| Shift+F11 | Step out (exit the current function). |
| F5 (while paused) | Resume execution to the next breakpoint. |
Using the debug Module
The debug module is a debugging utility distributed via npm. It creates loggers with namespaces and uses the environment variable DEBUG to control which namespaces are enabled. Unlike console.log, nothing is output in production unless the environment variable is set.
npm install debug
debug_module.js
var createDebug = require('debug');
// Create a debugger per namespace
var debugBattle = createDebug('dragonball:battle');
var debugPower = createDebug('dragonball:power');
var debugApp = createDebug('dragonball:app');
var fighters = [
{ name: 'Son Goku', power: 9000 },
{ name: 'Vegeta', power: 8000 },
{ name: 'Trunks', power: 7500 },
];
function battle(attacker, defender) {
debugBattle('Battle start: %s vs %s', attacker.name, defender.name);
debugPower('Attacker power: %d', attacker.power);
debugPower('Defender power: %d', defender.power);
if (attacker.power > defender.power) {
debugBattle('%s wins!', attacker.name);
return attacker;
} else {
debugBattle('%s wins!', defender.name);
return defender;
}
}
debugApp('Application started');
var winner = battle(fighters[0], fighters[1]);
console.log('Winner:', winner.name);
Specify the namespace to enable using the DEBUG environment variable when running.
DEBUG=dragonball:battle node debug_module.js dragonball:battle Battle start: Son Goku vs Vegeta +0ms dragonball:battle Son Goku wins! +1ms Winner: Son Goku DEBUG=dragonball:* node debug_module.js dragonball:app Application started +0ms dragonball:battle Battle start: Son Goku vs Vegeta +1ms dragonball:power Attacker power: 9000 +0ms dragonball:power Defender power: 8000 +0ms dragonball:battle Son Goku wins! +0ms Winner: Son Goku node debug_module.js Winner: Son Goku
Nothing is output when the DEBUG environment variable is not set. Use DEBUG=* to enable all namespaces. Multiple namespaces can be specified separated by commas (e.g., DEBUG=dragonball:battle,dragonball:power).
Summary
console.log() is convenient, but leaving too many in the code makes it messy, requiring eventual cleanup. Using the debug module allows environment variable control, so development logs do not carry over to production.
For serious debugging, node --inspect with Chrome DevTools or VSCode's built-in debugger is effective. Being able to stop execution at a breakpoint and inspect variables is a more efficient way to identify bugs than writing a large number of console.log statements.
Including the VSCode configuration file .vscode/launch.json in the repository allows all team members to start debugging with the same settings.
Common Mistakes
Common Mistake 1: Forgetting to Set the DEBUG Environment Variable
The debug module produces no output if the DEBUG environment variable is not set. When logs do not appear, it is tempting to think the code is wrong, but the cause is often a missing DEBUG setting.
Incorrect example (running without setting DEBUG):
var createDebug = require('debug');
var debug = createDebug('dragonball:battle');
debug('Battle start: Son Goku vs Vegeta');
node app.js
Nothing is output. The correct way to run is as follows.
DEBUG=dragonball:* node app.js dragonball:battle Battle start: Son Goku vs Vegeta +0ms
Common Mistake 2: Setting DEBUG=* in Production
Setting DEBUG=* in production causes all namespace debug logs to be output. Internal processing flows and configuration values become visible externally, and heavy log output degrades performance. In production, either do not set the DEBUG environment variable, or explicitly specify only the needed namespaces.
Incorrect example (production configuration file):
# .env.production (incorrect pattern) DEBUG=* NODE_ENV=production PORT=8080
Correct approach (do not set DEBUG in production):
# .env.production NODE_ENV=production PORT=8080
Common Mistake 3: Inconsistent Namespace Naming Conventions
Standardizing namespaces in the format appname:modulename allows filtering with DEBUG=dragonball:*. Inconsistent naming makes it difficult to enable logs for specific modules only.
Incorrect example (inconsistent naming):
var createDebug = require('debug');
// Inconsistent naming
var d1 = createDebug('battle'); // No app name
var d2 = createDebug('dragonball-power'); // Hyphen separator
var d3 = createDebug('DB'); // Uppercase abbreviation
Correct approach (standardize on appname:modulename format):
var createDebug = require('debug');
// Standardize on appname:modulename
var debugBattle = createDebug('dragonball:battle');
var debugPower = createDebug('dragonball:power');
var debugDb = createDebug('dragonball:db');
// All can be enabled at once with DEBUG=dragonball:*
If you find any errors or copyright issues, please contact us.