WebStorm Debugging: Complete Guide
Debugging is one of the most valuable skills a developer can master, and JetBrains' WebStorm ships with a powerful, deeply integrated debugger that can save you hours of console.log detective work. This guide walks you through everything from your first breakpoint to advanced remote debugging scenarios, with practical examples you can apply immediately.
What Is WebStorm Debugging?
WebStorm's debugger is a built-in tool that attaches to JavaScript and TypeScript runtimes — Node.js, browsers, and even Docker containers — allowing you to pause execution, inspect variables, evaluate expressions on the fly, and step through your code line by line. Unlike print-based debugging, the WebStorm debugger gives you a live snapshot of your application's state at any moment, making it far easier to reason about complex logic and asynchronous flows.
Under the hood, WebStorm uses the Chrome DevTools Protocol for browser debugging and the V8 Inspector Protocol for Node.js. This means you get the same low-level inspection capabilities as Chrome DevTools, but integrated directly into your IDE alongside your source code, version control, and tests.
Why Debugging in WebStorm Matters
- Speed: Setting a breakpoint is faster than writing, saving, and reloading
console.logstatements. - State inspection: You can examine the entire call stack, closures, and scope variables at any paused point.
- Live editing: Evaluate and modify variables during a debugging session without restarting the process.
- Conditional logic: Conditional breakpoints let you pause only when specific criteria are met, which is invaluable for loops and event handlers.
- Unified workflow: Everything happens inside WebStorm — no context switching between editor and browser DevTools.
Getting Started: Your First Debug Session
Let's start with a simple Node.js script. Create a file named calculator.js in your project:
// calculator.js
function calculateTotal(items, taxRate) {
let subtotal = 0;
for (const item of items) {
subtotal += item.price * item.quantity;
}
const tax = subtotal * taxRate;
return {
subtotal: subtotal,
tax: tax,
total: subtotal + tax
};
}
const cart = [
{ name: "Widget", price: 9.99, quantity: 3 },
{ name: "Gadget", price: 24.50, quantity: 1 },
{ name: "Sprocket", price: 4.25, quantity: 6 }
];
const result = calculateTotal(cart, 0.08);
console.log("Order summary:", result);
To debug this script, click in the gutter next to the line number on the subtotal += item.price * item.quantity; line. A red dot appears, indicating an active breakpoint. Now, instead of running the file normally, right-click anywhere in the editor and select Debug 'calculator.js'. WebStorm launches the script in debug mode and pauses execution when it hits your breakpoint.
Once paused, the Debug tool window opens at the bottom of the screen. Here you will see:
- Frames: The current call stack, showing which functions led to the paused line.
- Variables: All variables in the current scope, including local, closure, and global variables.
- Watches: A panel where you can add custom expressions to monitor.
- Console: An interactive console where you can evaluate expressions in the context of the paused frame.
Stepping Through Code
Once execution is paused, you control the flow using the stepping toolbar. These are the essential controls:
- Step Over (F8): Execute the current line without descending into any function it calls.
- Step Into (F7): Descend into the function called on the current line.
- Step Out (Shift+F8): Finish executing the current function and pause at the caller.
- Resume (F9): Continue execution until the next breakpoint.
- Run to Cursor (Alt+F9): Resume execution until the line where your cursor is placed.
Try stepping through the calculateTotal function. Press F8 repeatedly and watch the subtotal variable update in the Variables panel with each iteration of the loop. This immediate feedback is what makes the debugger so powerful for understanding how data flows through your code.
Conditional and Logging Breakpoints
Sometimes you only want to pause when a specific condition is true. Right-click on an existing breakpoint (or the gutter) and you will see a popup with advanced options. Enter a condition in the Condition field:
item.price > 20
Now the debugger only pauses when the current item's price exceeds 20. This is extremely useful when debugging loops that process hundreds of items but only misbehave on a few.
You can also create logging breakpoints (sometimes called tracepoints). These do not pause execution — instead, they log a message to the console each time the line is hit. Enable the Evaluate and log option and enter an expression:
`Processing ${item.name} - price: ${item.price}, qty: ${item.quantity}`
Logging breakpoints are a clean replacement for sprinkling console.log statements throughout your code, because they live in the debugger configuration rather than your source files.
Debugging Client-Side JavaScript in the Browser
WebStorm can debug JavaScript running in a browser. The most common approach is to create a JavaScript Debug run configuration. Go to Run > Edit Configurations, click the + button, and select JavaScript Debug. Enter the URL of your local dev server, for example http://localhost:3000.
Consider this simple front-end example:
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Todo Debug Demo</title>
</head>
<body>
<input id="taskInput" type="text" placeholder="Add a task" />
<button id="addBtn">Add</button>
<ul id="taskList"></ul>
<script src="app.js"></script>
</body>
</html>
// app.js
const tasks = [];
document.getElementById("addBtn").addEventListener("click", () => {
const input = document.getElementById("taskInput");
const value = input.value.trim();
if (value === "") {
return;
}
const task = {
id: Date.now(),
text: value,
completed: false
};
tasks.push(task);
renderTasks();
input.value = "";
});
function renderTasks() {
const list = document.getElementById("taskList");
list.innerHTML = "";
for (const task of tasks) {
const li = document.createElement("li");
li.textContent = task.text;
li.dataset.taskId = task.id;
li.addEventListener("click", () => {
task.completed = !task.completed;
li.style.textDecoration = task.completed ? "line-through" : "none";
});
list.appendChild(li);
}
}
Set a breakpoint inside the click handler on the line const value = input.value.trim();. Start your dev server, then run the JavaScript Debug configuration. WebStorm opens a Chromium-based browser (or your configured browser) and attaches the debugger. Click the Add button in the browser, and execution pauses inside WebStorm at your breakpoint. You can inspect the input element, the tasks array, and the DOM directly from the IDE.
Debugging Node.js Applications with Nodemon
In real projects, you often run Node servers with file watchers like nodemon. To debug these, create a Node.js run configuration and configure it to use nodemon. In Edit Configurations, add a new Node.js configuration, then set the following:
Node parameters: --inspect
JavaScript file: node_modules/nodemon/bin/nodemon.js
Application parameters: server.js
Here is a small Express server to demonstrate:
// server.js
const express = require("express");
const app = express();
const PORT = 3000;
app.use(express.json());
const users = [];
app.get("/users", (req, res) => {
res.json(users);
});
app.post("/users", (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: "Name and email are required" });
}
const existing = users.find(u => u.email === email);
if (existing) {
return res.status(409).json({ error: "User already exists" });
}
const user = {
id: users.length + 1,
name,
email,
createdAt: new Date().toISOString()
};
users.push(user);
res.status(201).json(user);
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Set a breakpoint on the line const existing = users.find(u => u.email === email);. Start the debug configuration, then send a POST request using curl or your HTTP client:
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}'
Execution pauses at your breakpoint. Inspect req.body in the Variables panel, step through the validation logic, and watch how the users array changes. Because you are using nodemon, any file changes will restart the server while keeping the debugger attached.
Debugging Tests
WebStorm integrates deeply with test runners like Jest, Mocha, and Vitest. You can debug individual tests by clicking the green gutter icon next to a test and selecting Debug. Consider this Jest test file:
// math.test.js
function fibonacci(n) {
if (n < 2) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
describe("fibonacci", () => {
it("returns correct values for small inputs", () => {
expect(fibonacci(0)).toBe(0);
expect(fibonacci(1)).toBe(1);
expect(fibonacci(5)).toBe(5);
expect(fibonacci(10)).toBe(55);
});
it("handles edge cases", () => {
expect(fibonacci(-1)).toBe(-1);
});
});
Set a breakpoint inside the fibonacci function on the return fibonacci(n - 1) + fibonacci(n - 2); line. Right-click the test file and select Debug 'math.test.js'. WebStorm runs Jest in debug mode and pauses at your breakpoint. You can step through recursive calls and watch how n changes with each invocation, which makes it trivial to understand why the edge case test fails.
Remote and Docker Debugging
For applications running inside Docker, WebStorm can attach to the Node inspector exposed by the container. First, ensure your Dockerfile or docker-compose configuration starts Node with the inspect flag:
# docker-compose.yml
version: "3.8"
services:
api:
build: .
ports:
- "3000:3000"
- "9229:9229"
command: node --inspect=0.0.0.0:9229 server.js
In WebStorm, create an Attach to Node.js/Chrome run configuration. Set the host to localhost and the port to 9229. Start your Docker container, then run the attach configuration. WebStorm connects to the remote inspector, and your breakpoints in the local source files map to the executing code inside the container. This workflow is essential for debugging microservices and production-like environments.
Using the Evaluate Expression Tool
One of the most underused features in WebStorm's debugger is the Evaluate Expression dialog, accessible via Alt+F8 while paused. It lets you run arbitrary JavaScript in the context of the current frame. For example, while paused inside the calculateTotal function, you could type:
items.filter(i => i.quantity > 2).map(i => i.name)
The result appears immediately, letting you prototype logic without modifying your source code. You can also use the inline Console in the Debug window for quick one-off evaluations.
Best Practices for Effective Debugging
- Start with a hypothesis: Before setting breakpoints, form a theory about what is wrong. This focuses your debugging and prevents random clicking.
- Use conditional breakpoints sparingly: Complex conditions can slow down execution significantly. If a condition is expensive, consider restructuring your code instead.
- Leverage Watches for key variables: Add important variables to the Watches panel so you do not have to dig through the scope tree on every pause.
- Clean up breakpoints: Remove breakpoints you no longer need. Use the Breakpoints dialog (Ctrl+Shift+F8) to manage them in one place.
- Learn the keyboard shortcuts: Memorize F8, F7, F9, and Alt+F8. Keyboard-driven debugging is dramatically faster than clicking toolbar buttons.
- Use exception breakpoints: Enable Pause on caught exceptions in the Breakpoints dialog to stop whenever an error is thrown, even if it is caught later. This catches silent failures early.
- Debug tests, not just application code: When a test fails, debug it instead of reading the assertion error repeatedly. Stepping through the test often reveals the bug faster than staring at output.
- Commit clean code: Never commit
debugger;statements or leftover logging breakpoints. Configure your linter to flagdebuggerstatements as errors.
Conclusion
WebStorm's debugger is a feature-rich tool that transforms how you investigate and fix bugs in JavaScript and TypeScript applications. By mastering breakpoints, stepping controls, conditional logic, expression evaluation, and remote attachment, you can diagnose issues in a fraction of the time it takes with traditional logging. The key is to make debugging a habit — reach for the debugger instead of console.log, and over time you will build an intuitive understanding of how your code executes. Start with the simple examples in this guide, then gradually incorporate advanced techniques like Docker attachment and exception breakpoints into your daily workflow. Happy debugging.