Node.js
Debugging
Application Development
JavaScript
Programming Tips

How do I debug Node.js applications?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Effective Node.js debugging is less about one tool and more about a repeatable workflow. You need fast local breakpoints, structured logs, and production-safe diagnostics when issues appear under real traffic. The best strategy combines the built in inspector, editor integration, and targeted observability.

Start with Built In Inspector

Node ships with an inspector protocol that supports breakpoints, step execution, and variable inspection.

Run script in inspect mode:

bash
node --inspect app.js

To break on first line:

bash
node --inspect-brk app.js

Then open chrome://inspect in Chrome and attach to the process.

Debug in VS Code with launch.json

For many teams, VS Code is the most efficient local debugging setup.

json
1{
2  "version": "0.2.0",
3  "configurations": [
4    {
5      "type": "node",
6      "request": "launch",
7      "name": "Debug API",
8      "program": "${workspaceFolder}/src/server.js",
9      "skipFiles": ["<node_internals>/**"],
10      "env": {
11        "NODE_ENV": "development"
12      }
13    }
14  ]
15}

Set breakpoints in route handlers, middleware, and service classes, then inspect call stack and variables live.

Use Structured Logging for Non Interactive Debugging

Breakpoints are great locally, but logs are essential in shared and production environments. Prefer structured logs over scattered console.log lines.

javascript
1const pino = require('pino');
2const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
3
4function getUser(req, res) {
5  logger.info({ requestId: req.headers['x-request-id'], userId: req.params.id }, 'getUser called');
6  res.json({ ok: true });
7}

Structured fields make filtering and correlation much easier in log systems.

Debug Async Code and Promises

Node errors often hide in async boundaries. Use these flags during investigations.

bash
node --trace-warnings --unhandled-rejections=strict src/server.js

Also wrap route handlers to capture async exceptions consistently.

javascript
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

Without consistent async handling, stack traces become fragmented.

Diagnose Performance and Event Loop Blocking

Some bugs are not logic errors but latency problems from blocking CPU work.

Quick event loop delay monitor:

javascript
1const { monitorEventLoopDelay } = require('node:perf_hooks');
2
3const h = monitorEventLoopDelay({ resolution: 20 });
4h.enable();
5
6setInterval(() => {
7  console.log('event loop p95 ms', (h.percentile(95) / 1e6).toFixed(2));
8  h.reset();
9}, 5000);

If delay spikes, profile hot paths with CPU profiler instead of random logging.

Debug Memory Growth

For suspected memory leaks, capture heap snapshots and compare object growth across time.

bash
node --inspect --max-old-space-size=512 src/server.js

Use Chrome DevTools memory tab to inspect retained objects. Focus on caches, listeners, and global maps that never release keys.

Debug Running Process in Container

In Docker or Kubernetes, expose inspector port only in trusted environments.

bash
node --inspect=0.0.0.0:9229 src/server.js

Then forward port securely from container to local machine and attach debugger. Never leave open inspector in public networks.

Build a Practical Debugging Playbook

A useful team playbook:

  1. reproduce issue with minimal input
  2. add structured logs around failing path
  3. attach debugger and inspect runtime state
  4. create automated regression test before fix
  5. verify in staging with production-like traffic

This process reduces repeated firefighting for same bug class.

Common Pitfalls

A common pitfall is relying only on console.log and removing logs before understanding root cause. Prefer structured logs that can stay with level controls.

Another issue is debugging in development mode only. Race conditions and timing bugs may appear only under production-like concurrency.

A third issue is attaching inspector to production without network controls. Inspector grants deep process access and is a security risk when exposed.

Teams also forget to write regression tests after fixing bugs, so the same issue returns in later refactors.

Summary

  • Use Node inspector and editor integration for local breakpoint debugging
  • Add structured logs for traceable diagnostics across environments
  • Handle async errors explicitly to preserve useful stack traces
  • Monitor event loop delay and memory for performance class bugs
  • Follow a repeatable debug playbook with regression tests after fixes

Course illustration
Course illustration