Debugging from VS Code

VS Code 550 ships js-debug, a CDP client with an editor around it. F5 on a JavaScript file runs node --inspect-brk for you; for anything real, commit a .vscode/launch.json.

.vscode/launch.json with a launch and an attach configurationJSON
{
  "version": "0.2.0",
  "configurations": [
    { "type": "node", "request": "launch", "name": "Run server",
      "program": "${workspaceFolder}/src/server.mjs", "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**", "${workspaceFolder}/node_modules/**"] },
    { "type": "node", "request": "attach", "name": "Attach", "port": 9229, "restart": true }
  ]
}

skipFiles keeps the stepper out of Node's internals — the difference between stepping into your handler and stepping into node:internal/streams. Use the attach configuration for a container or a process you started yourself: forward port 9229, and restart: true reconnects when it restarts.

Auto Attach removes even that step: debug.javascript.autoAttachFilter takes smart (scripts outside node_modules, plus known runners), always or onlyWithFlag, and the Toggle Auto Attach command switches between them. Two features beat breakpoints in a server: logpoints print an expression without pausing or editing the file, and conditional breakpoints stop only when order.id === 4711. The Caught Exceptions checkbox pauses the moment a rejection is created, before anyone swallows it.

Node's own CLI debugger speaks the same protocol when no editor is at hand.

A node inspect session finding the bug from Section 1.11.1Shell
$ node inspect cart.mjs
connecting to 127.0.0.1:9229 ... ok
debug> sb(16)
>16   return Math.round(cents * (1 - rate));
debug> cont
break in cart.mjs:16
debug> exec code
'save10'
debug> exec Object.keys(table)
[ 'SAVE10', 'SAVE25' ]