NodeJSDebugging in VS Code

Debugging in VS Code

VS Code has a first-class Node debugger built in — set a breakpoint by clicking in the gutter, hit F5, and step through your code with variables, call stack, and a watch panel right beside the source. It's the most productive way most developers debug Node day-to-day. This page covers the zero-config "JavaScript Debug Terminal", launch.json configurations for launching and attaching, debugging TypeScript with source maps, conditional and logpoint breakpoints, and debugging tests — all without leaving the editor.

The fastest start — the JavaScript Debug Terminal
Open a 'JavaScript Debug Terminal' and any node command you run is automatically debugged
The lowest-friction option needs no config at all. Open the command palette → "Debug: JavaScript Debug Terminal" (or pick it from the terminal dropdown). Any `node`, `npm`, or `yarn` command you run *in that terminal* is automatically launched with the inspector attached, so breakpoints you've set in the gutter just work. This is ideal for ad-hoc debugging — run `npm test` or `node script.js` and it stops at your breakpoints with no `launch.json` to write. For repeatable, shareable setups, you graduate to a `launch.json` configuration.
launch.json — launch configuration

.vscode/launch.json

JSON
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",          // "launch" = VS Code starts the process
      "name": "Debug app.js",
      "program": "${workspaceFolder}/app.js",
      "skipFiles": ["<node_internals>/**"],   // don't step into Node core
      "env": { "NODE_ENV": "development" },
      "console": "integratedTerminal"
    }
  ]
}
`request: launch` starts the process for you; `skipFiles` keeps you out of Node internals
A **launch** configuration tells VS Code to start the Node process itself with the inspector attached — press F5 and it runs `program` under the debugger. `skipFiles` with `<node_internals>/**` is almost always worth including: without it, "step into" can drop you into Node's own C++/JS internals, which is rarely what you want. Use `env` to set environment variables, `args` to pass CLI arguments, and `console: "integratedTerminal"` so your app's stdout/stdin behave normally. Commit `launch.json` so the whole team shares the same debug setup.
Attach to an already-running process

.vscode/launch.json — attach configuration

JSON
{
  "type": "node",
  "request": "attach",            // "attach" = connect to an existing process
  "name": "Attach to :9229",
  "port": 9229,
  "skipFiles": ["<node_internals>/**"]
}

Bash
# Start your app with the inspector, then run the "attach" config in VS Code:
node --inspect app.js
# For containers/remote, map "remoteRoot"/"localRoot" or tunnel port 9229.
Use `attach` for servers you already run (nodemon, Docker) — `launch` for one-shot scripts
An **attach** configuration connects VS Code to a process that's *already* running with `--inspect` (port 9229 by default). This is the right mode when your app is started elsewhere — by `nodemon` in watch mode, inside a Docker container, or on a remote host reached via an [SSH tunnel](/nodejs/node-inspector). For containers and remote hosts, set `localRoot` and `remoteRoot` so VS Code maps file paths between your machine and the remote filesystem; otherwise breakpoints won't bind to the right files. Launch vs attach is the same distinction as starting a process vs joining one.
Debugging TypeScript with source maps

tsconfig.json — emit source maps so breakpoints map to .ts lines

JSON
{ "compilerOptions": { "sourceMap": true, "outDir": "dist" } }

launch.json — run TS directly with tsx/ts-node

JSON
{
  "type": "node",
  "request": "launch",
  "name": "Debug TS",
  "runtimeExecutable": "node",
  "runtimeArgs": ["--import", "tsx"],     // or use "outFiles" with compiled JS
  "program": "${workspaceFolder}/src/index.ts",
  "outFiles": ["${workspaceFolder}/dist/**/*.js"]
}
No source maps = breakpoints in the wrong place — TS debugging depends on them
When you write TypeScript, Node actually runs *compiled JavaScript*, so the debugger needs **source maps** to translate a breakpoint on line 20 of your `.ts` file to the corresponding line in the emitted `.js`. Without `"sourceMap": true` (or a loader like `tsx`/`ts-node` that provides them), breakpoints either don't bind or land on the wrong lines, and the call stack shows compiled code. Set `outFiles` so VS Code knows where the compiled output lives. The same source-map requirement applies to any build step — bundlers, Babel — that transforms your source before Node runs it.
Breakpoint types beyond the basic stop

Type

What it does

Set via

Breakpoint

Pause when this line executes

Click the gutter

Conditional

Pause only when an expression is true

Right-click gutter → expression (e.g. id === 42)

Hit count

Pause after the line runs N times

Right-click gutter → hit count

Logpoint

Log a message without pausing — no code edit

Right-click gutter → logpoint {variable}

Caught/uncaught exceptions

Break the moment an error is thrown

Breakpoints panel checkboxes

Logpoints are `console.log` without editing code — and they never get committed
A **logpoint** is the cure for `console.log` debugging: right-click the gutter, choose Logpoint, and type a message like `order {order.id} total {total}`. VS Code prints it to the debug console every time that line runs, *without pausing* and *without modifying your source* — so there are no stray logs to forget and commit. **Conditional breakpoints** (`userId === 42`) are equally valuable: instead of pausing on every iteration of a thousand-item loop, you stop only on the case you care about. The exception breakpoints (break on caught/uncaught throws) jump you straight to where an error originates.
Debugging tests

launch.json — debug the node:test runner (or Jest/Mocha)

JSON
{
  "type": "node",
  "request": "launch",
  "name": "Debug tests",
  "program": "${workspaceFolder}/node_modules/.bin/jest",  // or "--test" with node
  "args": ["${fileBasenameNoExtension}", "--runInBand"],
  "console": "integratedTerminal",
  "skipFiles": ["<node_internals>/**"]
}
Debug the failing test, don't guess — set a breakpoint and step through it
When a [test](/nodejs/types-of-tests) fails mysteriously, stepping through it under the debugger beats adding logs and re-running. Point a launch config at your test runner's binary (`jest`, `mocha`, or `node --test`), set a breakpoint in the failing test or the code it exercises, and run the config. For Jest, `--runInBand` runs tests in a single process so breakpoints bind reliably. Most VS Code test extensions also add inline "Debug" buttons above each test — the same mechanism with less config.
Next
Debug in the browser-style DevTools UI, including memory and CPU profiling: [Debugging with Chrome DevTools](/nodejs/chrome-devtools).