Skip to content
C Codeloom
Node.js

Node Debugging with the Inspector

Step through Node.js code with Chrome DevTools and VS Code by attaching to the built-in inspector, including remote and production debugging tips.

·4 min read · By Codeloom
Beginner 8 min read

What you'll learn

  • How the Node inspector protocol works
  • Attaching from Chrome DevTools
  • Using the VS Code JavaScript debugger
  • Debugging production with safe flags
  • Heap snapshots and CPU profiles

Prerequisites

  • Comfortable with HTML and JavaScript

What and Why

console.log only takes you so far. Once a bug involves async timing, hot loops, or a memory leak, you need a real debugger. Node ships with the same V8 inspector protocol used by Chrome DevTools. With one flag your process exposes a debugging endpoint and you get breakpoints, stack traces, heap snapshots, and CPU profiles for free.

Mental Model

When you start Node with --inspect, V8 opens a WebSocket server on port 9229 that speaks the Chrome DevTools Protocol. Any client that knows the protocol can connect, pause execution, and inspect state. Chrome DevTools, VS Code, and IntelliJ are all clients. Multiple clients can attach to the same process at the same time.

Node process
+---------------+
|  V8 engine    |
|  inspector    |  <-- WebSocket ws://127.0.0.1:9229
+---------------+
      ^
      |
 DevTools client (Chrome, VS Code)
Inspector connection layout

Hands-on Example

Start any script with the inspector enabled.

node --inspect server.js

You will see output like Debugger listening on ws://127.0.0.1:9229/abc123. Open chrome://inspect in Chrome, click Configure, add localhost:9229, and your process appears under Remote Target. Click inspect and a DevTools window opens connected to your script.

To pause on the first line, use --inspect-brk instead. This is helpful for short-lived scripts that finish before you can attach.

node --inspect-brk migrate.js

VS Code makes this even easier. Open the JavaScript debug terminal from the command palette, then run your script normally. VS Code attaches the inspector automatically.

A simple example to try.

// pricing.js
function discount(price, percent) {
  const factor = (100 - percent) / 100;
  return price * factor;
}

const items = [{ price: 10 }, { price: 20 }, { price: 30 }];
let total = 0;
for (const item of items) {
  debugger;
  total += discount(item.price, 15);
}
console.log(total.toFixed(2));

The debugger; statement triggers a breakpoint when an inspector is attached and is ignored otherwise. You can hover over factor, step into discount, and watch total change.

For CPU work, record a profile. In DevTools open the Performance panel, hit record, exercise the code, and stop. The flame chart shows which functions dominate, which is far more useful than guessing.

For memory leaks, use the Memory panel to take a heap snapshot, run the suspected leak, take a second snapshot, and compare. Objects retained between snapshots are your suspects.

Common Pitfalls

Inspecting a production server with --inspect exposes a debugger to the network. Anyone on the same network can attach and run arbitrary code in your process. Always bind to localhost with --inspect=127.0.0.1:9229 and tunnel over SSH for remote debugging.

Source maps are easy to forget. If you debug compiled TypeScript and see strange line numbers, your build is probably not producing source maps. Set sourceMap: true in tsconfig.json and confirm the .map files exist next to your output.

debugger; statements left in committed code only run when an inspector is attached, but they still slow the engine slightly because V8 has to check at each statement. Remove them before merging.

Practical Tips

For long-running services, use the SIGUSR1 signal trick. Sending kill -SIGUSR1 PID tells a running Node process to open the inspector port without restarting. This is fantastic for debugging an issue that only happens after the server has been up for hours.

CPU profiling can run continuously with the inspector module from your own code. Capture a profile when an endpoint is slow and write it to disk for later analysis.

import { Session } from 'node:inspector/promises';
const session = new Session();
session.connect();
await session.post('Profiler.enable');
await session.post('Profiler.start');
// run the slow workload
const { profile } = await session.post('Profiler.stop');
await fs.writeFile('slow.cpuprofile', JSON.stringify(profile));

Open the file in Chrome DevTools to inspect it.

For memory diagnostics, the --heapsnapshot-near-heap-limit flag automatically writes a snapshot when you are about to crash with an out-of-memory error. This turns a useless crash into actionable evidence.

Wrap-up

The Node inspector turns guesswork into observation. With one flag you can step through code, profile CPU, and chase memory leaks using the same DevTools you already know from the browser. Keep the port bound to localhost, ship source maps with your builds, and learn to take snapshots, and most bugs become tractable in minutes rather than hours.