Back to Catalogue

Advanced Node.js debugging techniques

Debugging Node.js can be tricky and frustrating for developers. But fear not because this article’s advanced techniques that seamlessly fit into modern engineering tech stacks will help you debug your app like a pro.

16 May, 2023
post image

Debugging Node.js can be tricky and frustrating for developers. But fear not because this article’s advanced techniques that seamlessly fit into modern engineering tech stacks will help you debug your app like a pro.

From logging and using the debug module of Node.js to attaching to Node processes, setting up source maps, and utilizing breakpoints, your friendly neighborhood front-end development agency got it covered.

We'll also explore tools configuration, source map discovery and resolution, smart stepping, and profiling with Node.js. So grab your coffee and prepare to take your debugging skills to the next level!

Get developers that understand your business goals

On-time project delivery. Latest coding standards. Data security.

Learn more

Which debugging tools should I use to debug a Node.js app?

Here are the most useful debugging tools and techniques available in modern front-end development and back-end development.

Logging in Node.js

Logging in Node.js is like having a personal stenographer that writes down everything your code does, so you can investigate later when things go haywire. It’s a mechanism for recording important events that happen during the execution of a Node.js application, including errors, warnings, status updates, or anything else that might help you diagnose a problem or understand how your code works.

To utilize logging in Node.js, you can use built-in console functions like console.log(), console.error(), and console.warn(). You can also turn to third-party libraries like Winston, Bunyan, or Pino to provide more advanced logging functionality, such as log levels, log rotation, and transport to remote logging servers.

One of the key advantages of logging is that it's non-intrusive. You can add logging statements throughout your codebase without impacting the application's behavior or performance. You can also selectively enable or disable logging at runtime, making it easy to debug specific parts of an application or to turn off logging when it's no longer needed.

Optimizing your startup team during a downturn: come out on top by taking action today

The debug module of Node.js

The debug module of Node.js helps investigate code's behavior and identify the root cause of issues by providing granular control over debugging output. You can selectively enable or disable debugging statements using environment variables, making it easy to debug specific parts of an application or to turn off debugging output when it's no longer needed.

To utilize the debug module, you can include it in your Node.js application using npm and then use its API to create debugging statements throughout your codebase. Debugging statements are created by calling the debug function and passing in a unique identifier for that statement, which can be used to filter debugging output at runtime.

Here's an example of how to use the debug module to add debugging statements to your Node.js application. First, install the debug module using npm:

npm install debug

Then, require the debug module in your code and create a new debug instance:

const debug = require('debug')('myapp:server');

The argument to the debug function is the namespace for your code. In this case, we're using the namespace myapp:server. Add debug statements to your code using the debug function:

function add(a, b) {
debug('Adding %d + %d', a, b);
return a + b;
}
let result = add(2, 3);
debug('Result: %d', result);

The debug function takes a format string and any number of arguments to substitute into the string. In this case, we're using %d to format the a and b variables and the result variable. After that, run your Node.js application with the DEBUG environment variable set to the namespace(s) you want to enable debugging for:

DEBUG=myapp:* node index.js

This will enable debugging output for any namespaces that start with myapp:. You should see debugging output in your console or log files:

myapp:server Adding 2 + 3 +0ms
myapp:server Result: 5 +0ms

FREEBIE CTA 1

Debugging with Visual Studio Code

This next technique is a powerful tool that provides a full debugging environment, complete with breakpoints, watch expressions, real-time debugging output, and the same interface to edit code, manage source control, and debug.

To debug with Visual Studio Code, you can use the built-in debugger interface to configure debugging settings and launch your Node.js application. This tool will also provide you with advanced features like conditional breakpoints, allowing you to set breakpoints that only trigger when certain conditions are met. This can be useful for debugging complex logic or for identifying issues that occur under specific circumstances.

Attaching to Node.js

The process of attaching to a Node.js allows you to attach to a running Node.js application and debug it in real-time. This can be particularly useful for debugging issues that only occur under specific conditions or for debugging applications that are already running in production.

For example, if an application is experiencing issues only when running on a particular platform or handling a specific type of data, you can attach to the running process and reproduce those conditions to diagnose and fix the issue.

To attach to a running Node.js process, you can use the "Attach to Node Process" action in your debugging environment, such as Visual Studio Code. You can then select the process you want to attach to and specify the debugging options you want to use.

Here's an example of how to attach to a running Node.js process for debugging. Start your Node.js application without the --inspect flag:

node index.js

This will start the Node.js process without debug mode. Then, determine the process ID (PID) of the Node.js process you want to attach to:

ps aux | grep node

After that, attach to the Node.js process using the --inspect flag and the PID of the process:

node --inspect=<port> <script> <args>

For example: node --inspect=9229 --pid=<pid>

This action will attach the Node.js debugger to the running process and listen for debugging connections on the specified port (in this case, 9229).

  1. Open your preferred debugger client (e.g., Chrome DevTools) and connect to the debugging port.
  2. You should now be able to debug the running Node.js process as if it were started in debug mode from the beginning.

Attach to Node Process Action

This next action is a powerful feature of modern development environments that allows you to attach to a running Node.js process and debug it in real-time. It can be a lifesaver in identifying and fixing complex issues that are difficult to reproduce in a local development environment.

This tool will also allow you to debug applications already running in production. By attaching to the production process, you can debug issues without bringing the application down or taking it offline, minimizing downtime and ensuring that the application remains available to users.

To use the "Attach to Node Process" action, you first need to launch your Node.js application in debug mode, either by configuring a launch configuration in your development environment or by using command-line options. Once the application is running in debug mode, you can then use this action to attach to the running process and start debugging.

Setting up an “Attach” configuration

Setting up an "Attach" configuration allows you to quickly attach to a running Node.js process and start debugging without having to manually launch the application in debug mode. To set it up in Visual Studio Code, for example, you can follow these steps:

  1. Open your project in Visual Studio Code and navigate to the Debug view by clicking on the "Debug" icon on the left-hand side of the window.
  2. Click on the "Create a launch.json file" button to create a new launch configuration file.
  3. In that file, add a new configuration for "Attach" by copying the following code snippet:

{
"type": "node",
"request": "attach",
"name": "Attach",
" port": 9229
}

  1. Save the file.

Once you have it set up, you can quickly attach to a running Node.js process like this:

  1. Start your Node.js application in debug mode by running the command node --inspect or node --inspect-brk from the command line.
  2. In Visual Studio Code, navigate to the Debug view and select the "Attach" configuration you just created from the dropdown menu.
  3. Click on the "Start Debugging" button.

Using the V8 Inspector

The V8 inspector is a debugging protocol and API that provides a more powerful and flexible way to debug Node.js applications. It allows you to debug your Node.js code using a variety of debugging tools, including browser-based DevTools and standalone debugging clients. Here's an example of how to use the V8 inspector to debug a Node.js application:

Add the --inspect flag to your Node.js command-line arguments to start the Node.js process in debug mode and listen for connections on the default debugging port 9229:

node --inspect index.js

Then, open Google Chrome and navigate to chrome://inspect. This will open the DevTools window. Under the Remote Target section, you should see your Node.js process listed. Click on the "inspect" link to open the DevTools for your Node.js process.

The DevTools window will open with the Sources tab selected. You can now set breakpoints or inspect variables just like you would in the browser console. For example, you can set a breakpoint on the return statement in the add function by clicking on the line number to the left of the code:

function add(a, b) {
return a + b;

}
let result = add(2, 3);
console.log(result);

When the code hits the breakpoint, execution will pause, and the DevTools will open automatically. You can then inspect the value of the a and b parameters, step through the code, and examine the call stack. You can also use standalone debugging clients, such as the chrome-debugger package, to debug your Node.js application. For example, you can install the chrome-debugger package globally:

npm install -g chrome-debugger

And then start the Node.js process with the --inspect-brk flag:

node --inspect-brk index.js

This will start the Node.js process in debug mode and pause the execution at the first line of your code. Run the chrome-debugger command with the --port option to connect to the debugging port:

chrome-debugger --port=9229

Source maps

Source maps come in handy when working with compiled languages such as TypeScript or CoffeeScript because they provide a way to map the compiled code back to the original source code. To use them, you need to first enable them in your build process by adding a command-line option or a configuration setting to your build tool or build script.

Once source maps are enabled, you can use them in your debugging workflow by setting breakpoints in your source code and stepping through the compiled code as if it were the original source code. When a breakpoint is hit, the debugger will automatically switch to the corresponding source code, making it much easier to identify the root cause of the problems.

Tools configuration

Configuring your debugging tools properly involves stuff like setting breakpoints, configuring watches, and inspecting variables and data structures. One key tool is the debugger module, which provides a programmatic way to debug Node.js applications from within your code.

You can also configure watches to monitor variables and data structures for changes, set conditional breakpoints that only trigger when certain conditions are met, and configure debugging options such as hot-swapping code and inspecting closures.

Another important aspect of tool configuration is setting up your development environment. You need to configure your text editor or IDE to support debugging, as well as install any necessary plugins or extensions.

Source map discovery

Source map discovery is the process of automatically detecting and loading source maps for a Node.js application. This way, you can map the compiled code back to the original source code. One common approach to discovering and loading source maps in Node.js is to use a tool like the source-map-support module. You first need to install it via NPM:

npm install source-map-support

Once installed, you can use it in your app code to automatically load source maps for your compiled code:

const sourceMapSupport = require('source-map-support');

sourceMapSupport.install();

There are also other tools and libraries available. For example, the source-map-loader module provides a way to automatically load source maps for Webpack-compiled code, while the babel-source-map-support module provides a way to load source maps for Babel-compiled code.

Source map resolution

This next process requires mapping the compiled code back to the original source code using the source maps. When a source map is loaded, it typically contains information about the mapping between the compiled code and the original source code, such as the file paths and line numbers of the original source code, as well as the corresponding locations in the compiled code.

To perform source map resolution, the debugger needs to use this info to map the compiled code back to the original source code by identifying the location of the compiled code in the source map and then using the mapping information to determine the corresponding location in the original source code.

For example, suppose you have a compiled JavaScript file named app.js that was generated from the original source file app.ts. When you set a breakpoint in app.js, the debugger needs to perform source map resolution to determine the corresponding location in app.ts.

To perform source map resolution in a debugger like Visual Studio Code, you typically need to enable source map support and ensure that the source maps are correctly configured and loaded. Once done, the debugger will automatically perform source map resolution as you step through your code or interact with breakpoints.

Smart stepping

Smart stepping is a debugging technique that uses advanced stepping options to skip over unimportant code and focus on the parts of your code that are most relevant to the current debugging session. There are several options available:

  • “Step Into Targets” allows you to specify which functions you want to step into when you encounter a function call in your code.
  • “Step Out” lets you quickly step out of a function and return to the calling function.
  • “Continue to Here” allows you to skip over a block of code and continue execution from a specific point in your code.
  • “Smart Breakpoints” set breakpoints that are conditional on specific conditions in your code.

Get developers that understand your business goals

On-time project delivery. Latest coding standards. Data security.

Learn more

JavaScript source map tips

Source maps are a powerful tool for debugging JavaScript applications, but they can be tricky to work with if you're unfamiliar with how they work. Here are some tips to help you get the most out of JavaScript source maps:

  1. Make sure source maps are enabled and configured correctly. Check the documentation for your specific tool or framework.
  2. Use source maps with a debugger.
  3. Be aware of source map limitations. They can be inaccurate or incomplete in some cases, particularly if your code is heavily optimized or if you are using certain types of build tools.
  4. Make sure that the file paths in your source maps are correct and match the file paths of your original source code. If the file paths are incorrect, your debugger will not be able to find the original source code.
  5. Use source map plugins to help you work with source maps more easily.

Utilizing breakpoints

Breakpoints allow you to pause the execution of your code at specific points, giving you the opportunity to inspect the state of your application and diagnose any problems. Here are some tips:

  1. Use conditional breakpoints to set conditions that must be met before the breakpoint is triggered.
  2. Use multiple breakpoints to narrow down the location of a problem more quickly.
  3. Use debugger statements, which are special statements that can be added to your code to trigger a breakpoint when executed.
  4. Use logpoints. They are like breakpoints, but instead of pausing the execution of your code, they log a message to the console.
  5. Disable or remove breakpoints. Too many breakpoints can slow down your debugging session and make it harder to navigate your code.
FREEBIE CTA 1

Profiling with Node.js

And the last technique is profiling. It’s the process of analyzing the performance of your Node.js application in order to identify areas of slow or inefficient code. Here’s what you can do:

  • Use the built-in profiling tools that will allow you to profile your application using the --prof flag. This will generate a profiling log file that can be analyzed using other tools.
  • Use a third-party profiling tool, such as the Chrome DevTools or the Node.js Profiler. They provide a visual interface for profiling your application and can help you identify performance issues more easily.
  • Focus on specific areas of your code instead of profiling your entire application.
  • Monitor resource usage, such as CPU and memory usage.
  • Once you have generated a profiling log file, analyze the data to identify performance issues. Look for functions that take up a lot of CPU time or are being called frequently.

Get developers that understand your business goals

On-time project delivery. Latest coding standards. Data security.

Learn more

Conclusion

Debugging Node.js applications can be both an art and a science. With many tools and techniques at your disposal, Node.js debugging has many possibilities.

Whether you're logging like a lumberjack or diving deep into source maps like a treasure hunter, there's no limit to how you can uncover the hidden secrets of your code.

From the tried-and-true debug module to the visual delights of Visual Studio Code, the debugging landscape is ever-changing and constantly evolving.

call to action image

Design packages for your startup

Ideal for early-stage product UIs and websites.

See pricing
author

CEO and Founder of Merge

My mission is to help startups build software, experiment with new features, and bring their product vision to life.

My mission is to help startups build software, experiment with new features, and bring their product vision to life.

You may be interested in

Let’s take this to your inbox

Join our newsletter for expert tips on growth, product design, conversion tactics, and the latest in tech.