Programming Beginner 7 min

How to Debug Node.js Code in VS Code with Breakpoints

Most developers debug Node.js the same way they learned to code: console.log everywhere. It works — right up until the bug is inside a loop with 10,000 iterations, or inside a callback you cannot easily intercept, or dependent on state that is built up across several function calls.

VS Code has a full-featured debugger built in. With a breakpoint you can pause the program at any line, inspect every variable in scope, step through code one instruction at a time, and evaluate expressions on the fly. This guide gets you from zero to a working debug session in minutes.

Step-by-step

  1. 1

    Open the Run and Debug Panel

    Click the bug icon in the Activity Bar on the left side of VS Code, or press Ctrl+Shift+D (Cmd+Shift+D on Mac). This opens the Run and Debug panel. If you have never configured a debugger for this project, you will see a button that says create a launch.json file — click it.

    javascript
    // Keyboard shortcuts
    // Open Run & Debug panel:  Ctrl+Shift+D  (Cmd+Shift+D on Mac)
    // Start debugging:         F5
    // Stop debugging:          Shift+F5
    // Step over (next line):   F10
    // Step into (go inside):   F11
    // Step out (go up):        Shift+F11
    // Continue to breakpoint:  F5 (while paused)
  2. 2

    Generate a launch.json File

    When prompted to select an environment, choose Node.js. VS Code creates .vscode/launch.json with a default configuration. The key field is "program" — set it to the entry point of your application. The ${workspaceFolder} variable resolves to the root of your open project.

    json
    // .vscode/launch.json
    {
        "version": "0.2.0",
        "configurations": [
            {
                "type": "node",
                "request": "launch",
                "name": "Launch Program",
                "skipFiles": ["<node_internals>/**"],
                "program": "${workspaceFolder}/src/index.js"
                // For TypeScript with ts-node:
                // "program": "${workspaceFolder}/src/index.ts",
                // "runtimeArgs": ["-r", "ts-node/register"]
            }
        ]
    }
  3. 3

    Set a Breakpoint

    Open the file where you want to pause execution. Click in the gutter — the empty space to the left of the line numbers. A red dot appears, indicating a breakpoint. Click it again to remove it. You can set as many breakpoints as you need across any number of files before starting the debugger.

    Breakpoints persist between debug sessions (they are saved in VS Code's workspace state), so you do not have to re-add them every time.

    javascript
    // Example: src/index.js
    // Set a breakpoint on line 12 by clicking the gutter
    
    const express = require('express');
    const app = express();
    
    app.get('/users/:id', async (req, res) => {
        const userId = parseInt(req.params.id);   // <- breakpoint here
        const user = await getUserById(userId);
    
        if (!user) {
            return res.status(404).json({ error: 'Not found' });
        }
    
        res.json(user);
    });
  4. 4

    Start the Debugger and Inspect Variables

    Press F5 or click the green play button in the Run and Debug panel. The program starts and execution pauses at your breakpoint. The left panel now shows three sections:

    • Variables — all variables in scope at the current line, with their current values. Expand objects and arrays.
    • Watch — expressions you add manually. Click + to add any JS expression; it evaluates continuously as you step.
    • Call Stack — every function call that led to the current line. Click any frame to inspect the variables at that level.
    javascript
    // When paused at a breakpoint:
    // - Hover over any variable to see its value in a tooltip
    // - In the Watch panel, add expressions like:
    //     user.email
    //     req.headers['authorization']
    //     Array.isArray(results)
    //     results.length > 0
    
    // The Debug Console (bottom panel) lets you run arbitrary code
    // in the paused context — great for one-off inspections:
    // > user
    // > Object.keys(req.body)
    // > new Date(user.created_at).toISOString()
  5. 5

    Step Through Code

    While paused, you control execution line by line:

    • F10 (Step Over) — execute the current line and move to the next. If the line calls a function, run it without entering it.
    • F11 (Step Into) — if the current line calls a function, enter that function and pause at its first line.
    • Shift+F11 (Step Out) — finish executing the current function and pause at the line that called it.
    • F5 (Continue) — resume until the next breakpoint or the program ends.
    javascript
    // Typical stepping workflow:
    // 1. F5  → start; pauses at first breakpoint
    // 2. F10 → step over to next line, inspect variables
    // 3. F11 → step INTO getUserById() to see what happens inside
    // 4. F10 → step through lines inside getUserById()
    // 5. Shift+F11 → step OUT back to the calling function
    // 6. F5  → continue to next breakpoint or program end
    
    // Use F10 most of the time.
    // Use F11 only when you need to go inside a specific function.
    // Use Shift+F11 to get out of a utility function quickly.
  6. 6

    Add Conditional Breakpoints

    A regular breakpoint pauses every time the line is hit. A conditional breakpoint pauses only when an expression is true. This is essential for debugging inside loops — you do not want to press F5 a thousand times to reach the iteration where things go wrong.

    Right-click an existing breakpoint dot (or right-click in the gutter) and select Add Conditional Breakpoint or Edit Breakpoint. Enter any JavaScript expression.

    javascript
    // Right-click the breakpoint → Edit Breakpoint → Expression
    
    // Pause only when i equals 500
    // Expression: i === 500
    
    // Pause only when user is null
    // Expression: user === null
    
    // Pause when a specific ID is processed
    // Expression: req.params.id === '42'
    
    // Hit Count: pause every N times the line is hit
    // (separate option in the same menu)
    // Useful for: pause every 100th iteration
  7. 7

    Enable Auto Attach for Terminal Processes

    Auto Attach lets VS Code automatically debug any Node.js process you start in the integrated terminal — no launch.json needed. Open the Command Palette (Ctrl+Shift+P) and run Debug: Toggle Auto Attach. Set it to Smart (attaches to node processes in your workspace, ignores global tools).

    After enabling it, open an integrated terminal and run node src/index.js — VS Code attaches automatically and your existing breakpoints work immediately.

    bash
    // Enable Auto Attach:
    // Ctrl+Shift+P → "Debug: Toggle Auto Attach" → Smart
    
    // Then in the integrated terminal:
    node src/index.js
    // VS Code automatically attaches — breakpoints are active
    
    // Works with npm scripts too:
    npm run dev
    // As long as the underlying process is Node.js, it attaches
    
    // Works with nodemon:
    nodemon src/index.js
    // Each restart re-attaches automatically
  8. 8

    Attach to an Already-Running Process

    If the Node process is already running (started outside VS Code, or a Docker container), you can attach to it instead of launching a new one. Start Node with the inspect flag, then add an "Attach" configuration to launch.json.

    javascript
    // 1. Start Node with the inspector
    node --inspect src/index.js
    // Listening on ws://127.0.0.1:9229/...
    
    // Or for immediate pause on first line:
    node --inspect-brk src/index.js
    
    // 2. Add to .vscode/launch.json
    {
        "type": "node",
        "request": "attach",
        "name": "Attach to Running Node",
        "port": 9229,
        "skipFiles": ["<node_internals>/**"]
    }
    
    // 3. In VS Code: F5 → select "Attach to Running Node"
    // Breakpoints activate immediately

Tips & gotchas

  • Add <code>"skipFiles": ["&lt;node_internals&gt;/**", "${workspaceFolder}/node_modules/**"]</code> to your launch config — this prevents the debugger from stepping into Node built-ins and third-party packages.
  • The Debug Console at the bottom executes code in the current paused context. Use it to test a fix on the fly before writing it — faster than edit-save-restart.
  • For TypeScript projects, set <code>"sourceMap": true</code> in <code>tsconfig.json</code> and add <code>"outFiles": ["${workspaceFolder}/dist/**/*.js"]</code> to launch.json so VS Code maps compiled JS back to your TS source.
  • Logpoints (right-click gutter → Add Logpoint) print a message to the Debug Console without pausing execution — like a non-intrusive <code>console.log</code> that does not require a code change.
  • <code>node --inspect-brk</code> pauses on the very first line of the script before any code runs — useful when the crash happens during module initialization.

Wrapping up

The VS Code debugger is one of those tools that feels slow to set up until you use it once on a real bug. After that, going back to console.log feels like debugging with your eyes closed. Spend ten minutes with launch.json and breakpoints, and you will never approach a difficult Node.js bug the same way again.

#Node.js #Debugging #VS Code
Back to all guides

Need Help With Your Project?

Book a free 30-minute consultation to discuss your technical challenges and explore solutions together.