Debugging and profiling Gadget backends 

Gadget's CLI, ggt, includes a ggt debugger command that allows you to debug or profile your Gadget backend.

Setup 

Before you can start debugging or profiling, you need to have ggt installed and your project's files pulled down locally.

  1. Install ggt locally if you haven't already:
terminal
npm install -g ggt
  1. Use ggt dev to pull down your project's files locally:
terminal
ggt dev --app <app-name> --env <env-name>

For more information on ggt, see the CLI guide.

Debugging 

ggt debugger configures your editor to enable debugging of your Gadget app's backend actions, HTTP routes, and loader functions. Once configured, you can set breakpoints in your code and debug your application directly from your editor.

There are built-in launch configurations for VS Code and its forks, but you can use any editor that supports the Chrome DevTools Protocol. Other editors may require additional configuration.

Initial setup 

To configure debugging, run the following command in your project directory:

terminal
ggt debugger --configure vscode

This will add .vscode/launch.json and .vscode/tasks.json config files to your project. The --configure flag only needs to be run once per project.

ggt debugger --configure will fail if you already have .vscode/tasks.json or .vscode/launch.json files in your project. You'll need to remove or rename these files before running the configuration command, then manually merge existing configuration.

Starting the debugger 

After initial configuration, you can start debugging by running the Gadget debugger launch configuration from your editor. Any breakpoints you set in your editor will be hit when the debugger is attached.

Profiling 

Your Gadget backend runs on Node.js, which means a Node.js profiler can be used to get detailed information about the performance of your actions and routes.

A profiler can help you identify bottlenecks and computationally expensive functions or third party libraries, optimize your actions, and improve the performance of your backend.

Starting the profiler 

You need to use ggt to set up a connection to your backend, then a Node.js profiler like Chrome DevTools to capture and analyze the profile.

  1. Run ggt debugger in your local terminal to start a websocket connection to your backend:
terminal
ggt debugger

The localhost address for the websocket connection will be listed in your terminal. Use this address to connect your Node.js profiler to your backend. This guide will use Chrome DevTools to profile your backend.

  1. Open Chrome and navigate to chrome://inspect. You should see your localhost address listed as a target.
  2. Click inspect under your localhost address to open the Chrome DevTools inspector for your backend.

You are now set up and ready to start profiling your backend.

Profiling CPU time 

The Node.js profiler is a sampling profiler. It works by capturing a stack trace of the JavaScript code running in your backend at regular intervals.

This means it is important to run the code you want to profile multiple times to get a representative sample of the code's execution. A single execution of an action may not be enough for the profiler to capture an accurate profile.

To capture a profile for an action, you can:

  1. Start the profiler in the Performance tab of Chrome DevTools.
  2. Use the API playground in the Gadget editor to call your backend action or route multiple times:
Run your action multiple times to generate a profile
JavaScript
for (let i of Array(100)) { await api.generateReport(); }
  1. Stop the profiler in Chrome DevTools to finish capturing the profile.
  2. (Optional) Save the profile locally.

Now you can analyze your profile.

Getting a representative sample

You need enough runs of your action or route to get a representative sample. If you see functions appearing and disappearing between runs, or if the relative time spent in functions keeps changing, you need more runs. The profile should show consistent patterns before you can trust the results.

Tips for generating a good profile 

There are things you can do to generate a more useful profile.

  • It is worth repeating: run the code you want to profile multiple times to get a representative sample of the code's execution. A single execution of an action may not be enough for the profiler to capture an accurate profile.
  • Break down your code into smaller functions so the profile captures more granular information. Profilers display the time spent in each function, so the more granular the functions, the more detailed the profile.

Analyzing your profile 

Once you have captured a profile of your actions, you can start to analyze it and identify bottlenecks in your code.

The Chrome DevTools profiler shows two key metrics for each function:

  • Total time: The CPU time spent in a function and all functions it calls. If functionA() calls functionB(), the total time for functionA includes all the time spent executing functionB.
  • Self time: The CPU time spent only in that function's own code, excluding any time spent in called functions.

What to look for 

Focus on functions that combine high self time with high call frequency, these are your biggest optimization opportunities. For example, a function with 30% self time called 1000 times is a better target than one with 50% self time called once.

Functions with high total time but low self time are calling expensive subfunctions, drill down into what they call. Look for patterns like nested loops, repeated calculations, or inefficient data transformations.

If you see expensive functions in third-party libraries, look at the call stack to see what your code is calling that leads to those expensive library functions. You may be able to optimize how you're using the library, or find an alternative approach.

How to take action 

Once you've identified a bottleneck, here are common optimization strategies:

  • Cache expensive computations: If you're recalculating the same value repeatedly, cache it, or store the result in the database.
  • Reduce iterations: Look for opportunities to reduce loop iterations or use more efficient algorithms. Computed views can be helpful for reading and aggregating large sets of data from the database.
  • Batch operations: Instead of processing items one-by-one, batch them together. Your app API includes bulk action endpoints that can and should be used, including bulk endpoints for background actions.
  • Optimize data structures: Use the right data structure for your use case, for example, Set for speedy lookups, Map for key-value pairs.
  • Move work outside hot paths: If possible, precompute values or move expensive operations to less frequently called code.

After making changes, re-profile your code to verify the improvement. Compare the self time and total time of the optimized functions before and after your changes.

More information 

See the Google Chrome DevTools docs for more information on using the Node.js profiler.

How profiler times relate to Gadget billing 

The times shown in the profiler represent CPU time, not wall-clock time. This is the same metric Gadget uses for billing, Gadget only bills for actual CPU time consumed by your code.

Importantly, idle time is not billed. When your code is waiting for async operations like network requests, database queries, or file I/O, the CPU is idle and no time accumulates. This means:

  • A function with high total time but low self time is calling expensive subfunctions—look at what it calls.
  • A function with high self time is doing CPU-intensive work itself—optimize its logic directly.
  • Time spent awaiting external services won't affect your CPU billing.

Was this page helpful?