Debugging Node.js apps with Visual Studio Code debugger

Rather than doing it with console logs, debugging with a debugger and breakpoints is recommended. VSCode provides a built-in debugger for JavaScript-based apps.

This post covers configuring and running a debugger for various Node.js apps in VSCode.

Configuration basics

VSCode configurations can use runtime executables like npm and ts-node. The executables mentioned above should be installed globally before running the configurations.

A configuration can use the program field to point to the binary executable package inside node_modules directory to avoid installing packages globally.

Runtime executables and programs can have arguments defined in runtimeArgs and args fields, respectively.

A configuration can have different requests:

  • attach - the debugger is attached to the running process

  • launch - the debugger launches a new process and wraps it

There are multiple configurations:

  • node - runs the program from program field, and logs are shown in debug console

  • node-terminal - runs the command from command field and shows the logs in the terminal

The configuration file is .vscode/launch.json. The selected configuration on the Run and Debug tab is used as the default one.

Launch configs

Node

Below are examples of configurations for running Node processes with the debugger.

{
  "version": "0.2.0",
  "configurations": [
    // ...
    {
      "name": "Launch script in debug console",
      "program": "index.js", // update entry point
      "request": "launch",
      "type": "node",
      "skipFiles": [
        "<node_internals>/**"
      ]
    },
    {
      "name": "Launch script in the terminal",
      "command": "node index.js", // update entry point
      "request": "launch",
      "type": "node-terminal",
      "skipFiles": [
        "<node_internals>/**"
      ]
    }
  ]
}

Running npm scripts in debug mode

The debugger launches the following script in both configurations, dev in this case.

{
  "version": "0.2.0",
  "configurations": [
    // ...
    {
      "name": "Launch dev script in debug console",
      "runtimeExecutable": "npm",
      "runtimeArgs": [
        "run",
        "dev"
      ],
      "request": "launch",
      "type": "node",
      "skipFiles": [
        "<node_internals>/**"
      ]
    },
    {
      "name": "Launch dev script in the terminal",
      "command": "npm run dev",
      "request": "launch",
      "type": "node-terminal"
    }
  ]
}

ts-node

The following configurations will wrap the debugger around ts-node entry point.

{
  "version": "0.2.0",
  "configurations": [
    // ...
    {
      "name": "Launch ts-node script in debug console",
      "program": "node_modules/.bin/ts-node",
      "args": ["index.ts"], // update entry point
      "request": "launch",
      "type": "node",
      "skipFiles": [
        "<node_internals>/**"
      ]
    },
    {
      "name": "Launch ts-node script in the terminal",
      "command": "ts-node index.ts", // update entrypoint
      "request": "launch",
      "type": "node-terminal",
      "skipFiles": [
        "<node_internals>/**"
      ]
    }
  ]
}

@babel/node

The following configuration will wrap the debugger around babel-node entry point.

{
  "version": "0.2.0",
  "configurations": [
    // ...
    {
      "name": "Launch babel-node script in debug console",
      "program": "node_modules/.bin/babel-node",
      "args": ["src"], // update entry point
      "request": "launch",
      "type": "node",
      "skipFiles": [
        "<node_internals>/**"
      ]
    }
  ]
}

Nodemon

Add new configuration with Run → Add configuration option, select Node.js: Nodemon Setup.

Update program field to point to the nodemon executable package, and add arguments with args field to point to the entry point.

{
  "version": "0.2.0",
  "configurations": [
    // ...
    {
      "name": "Launch nodemon script in debug console",
      "program": "node_modules/.bin/nodemon",
      "args": ["-r", "dotenv/config", "--exec", "babel-node", "src/index.js"], // update entry point
      "request": "launch",
      "type": "node",
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "restart": true,
      "skipFiles": [
        "<node_internals>/**"
      ]
    }
  ]
}

Mocha

Add a new configuration, and choose Node.js: Mocha Tests configuration.

Replace tdd with bdd as u parameter and program field to point to the mocha executable package.

{
  "version": "0.2.0",
  "configurations": [
    // ...
    {
      "name": "Launch mocha tests",
      "program": "node_modules/.bin/mocha",
      "args": [
        "-u",
        "bdd",
        "--timeout",
        "999999",
        "--colors",
        "test"
      ],
      "request": "launch",
      "type": "node",
      "internalConsoleOptions": "openOnSessionStart",
      "skipFiles": [
        "<node_internals>/**"
      ]
    }
  ]
}

Attach configs

Auto Attach should be activated in settings with the With Flag value. In that case, auto-attaching is done when --inspect flag is given.

The debugger should be attached when some of the following scripts are executed.

{
  // ...
  "scripts": {
    // ...
    "start:debug": "node --inspect index.js", // update entry point
  }
}
Jest
{
  // ...
  "scripts": {
    // ...
    "test:debug": "node --inspect -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
  }
}
NestJS
{
  // ...
  "scripts": {
    // ...
    "start:debug": "nest start --debug --watch"
  }
}

Debugging basics

During the debugging, the variables tab shows local variables. The step over option goes to the following statement in the codebase, while step into option goes deeper into the current statement.

Log points can add logs in debug console when a certain part of the codebase is executed without pausing the process.

Boilerplate

Here is the link to the boilerplate I use for the development.

Did you find this article valuable?

Support Željko Šević by becoming a sponsor. Any amount is appreciated!