Node.js network bugs often hide between layers: a CLI inherits one proxy setting, a test runner starts with a stale environment, axios is configured differently from got, or HTTPS fails because the process cannot find the CA bundle that trusts your local debugging certificate.
If you need to debug Node.js API requests on macOS, make one known request visible first. Once Rockxy captures that request, you can inspect the real method, URL, headers, body, response, and timing without adding temporary logging across your app.
Rockxy's Node.js Developer Setup flow covers three common client paths: axios, built-in Node core HTTP/HTTPS, and got. The docs include the same localhost demo shown here: Node.js setup guide.
For a runnable project, use Rockxy-NodeJS-Sample-Guidance. It is a Node 20+ ESM sample with exact dependencies, a local demo API, example files for each client, and tests that keep the entry points honest.
Why Node.js traffic can disappear
The failure is usually not mysterious. One terminal has HTTP_PROXY and another does not. A package reads environment proxy variables while another requires explicit options. A local API runs on 8000, Rockxy listens on 8888, and those ports get swapped. Or a request succeeds directly, so there is nothing for Rockxy to capture.
That is why the first demo should be plain HTTP to a local target. No DNS, no external dependency, no certificate trust. Just a Node client targeting http://127.0.0.1:8000/demo through Rockxy's active proxy port.
Start with the sample repo
Start Rockxy, enable capture, and copy the active proxy port from the toolbar. In the screenshot below, Rockxy is listening on 8888. Your app may use a different port, so read the current value from Rockxy.
Clone and install the sample project:
git clone https://github.com/RockxyApp/Rockxy-NodeJS-Sample-Guidance.git
cd Rockxy-NodeJS-Sample-Guidance
npm ci
Run the local demo server in the first terminal:
node scripts/demo-server.js
In a second terminal, point one client at Rockxy and the local demo endpoint:
export ROCKXY_PROXY_URL="http://127.0.0.1:8888"
export ROCKXY_TARGET_URL="http://127.0.0.1:8000/demo"
unset ROCKXY_CA_BUNDLE
node examples/axios-example.js
The expected terminal result is intentionally simple:
status=200
{
"message": "Hello from the Rockxy Node.js localhost demo.",
"path": "/demo",
"host": "127.0.0.1:8000"
}
Then run the other clients against the same target:
node examples/node-core-example.js
node examples/got-example.js
node scripts/run-all-examples.js
What success looks like in Rockxy
The useful part happens in Rockxy: the traffic list shows 127.0.0.1/demo, the selected request shows GET 200 OK http://127.0.0.1:8000/demo, and the headers identify the Node sample client that sent it.
GET /demo to 127.0.0.1:8000. The selected flow proves the client, proxy, and demo server are connected.This is the baseline. Before debugging auth, retries, request bodies, workers, or TLS, get to this state. If the local HTTP request is not visible, fix routing before touching application code.
Use explicit proxy settings in real code
The sample uses explicit proxy and certificate options because they make debugging predictable. Environment proxy support varies across packages and Node versions; explicit settings show the next developer exactly where traffic goes.
For axios, split the proxy URL into the fields axios expects and add a CA-backed HTTPS agent only when you inspect HTTPS:
import axios from 'axios';
import { readFileSync } from 'node:fs';
import { Agent as HttpsAgent } from 'node:https';
const proxyUrl = new URL(process.env.ROCKXY_PROXY_URL ?? 'http://127.0.0.1:8888');
const targetUrl = process.env.ROCKXY_TARGET_URL ?? 'http://127.0.0.1:8000/demo';
const caBundle = process.env.ROCKXY_CA_BUNDLE;
const response = await axios.get(targetUrl, {
proxy: {
protocol: proxyUrl.protocol.replace(':', ''),
host: proxyUrl.hostname,
port: Number(proxyUrl.port),
},
httpsAgent: caBundle
? new HttpsAgent({ ca: readFileSync(caBundle) })
: undefined,
timeout: 10_000,
headers: { 'User-Agent': 'Rockxy-NodeJS-Sample/axios' },
});
console.log(`status=${response.status}`);
console.log(response.data);
For built-in Node core, a plain HTTP proxy request uses the absolute target URL as the request path. That is the important bit many hand-written examples miss:
import http from 'node:http';
const proxyUrl = new URL(process.env.ROCKXY_PROXY_URL ?? 'http://127.0.0.1:8888');
const targetUrl = new URL(process.env.ROCKXY_TARGET_URL ?? 'http://127.0.0.1:8000/demo');
const request = http.request(
{
hostname: proxyUrl.hostname,
port: Number(proxyUrl.port),
method: 'GET',
path: targetUrl.href,
headers: {
Host: targetUrl.host,
'User-Agent': 'Rockxy-NodeJS-Sample/node-core',
},
timeout: 10_000,
},
(response) => {
let body = '';
response.setEncoding('utf8');
response.on('data', (chunk) => { body += chunk; });
response.on('end', () => {
console.log(`status=${response.statusCode}`);
console.log(body);
});
},
);
request.on('error', (error) => {
console.error(error);
process.exitCode = 1;
});
request.end();
For got, use proxy agents for both HTTP and HTTPS, then pass the Rockxy CA bundle through certificateAuthority when the target is HTTPS:
import { readFileSync } from 'node:fs';
import got from 'got';
import { HttpProxyAgent } from 'http-proxy-agent';
import { HttpsProxyAgent } from 'https-proxy-agent';
const proxyUrl = process.env.ROCKXY_PROXY_URL ?? 'http://127.0.0.1:8888';
const targetUrl = process.env.ROCKXY_TARGET_URL ?? 'http://127.0.0.1:8000/demo';
const caBundle = process.env.ROCKXY_CA_BUNDLE;
const response = await got(targetUrl, {
agent: {
http: new HttpProxyAgent(proxyUrl),
https: new HttpsProxyAgent(proxyUrl),
},
https: {
certificateAuthority: caBundle ? readFileSync(caBundle) : undefined,
},
timeout: { request: 10_000 },
headers: { 'User-Agent': 'Rockxy-NodeJS-Sample/got' },
});
console.log(`status=${response.statusCode}`);
console.log(response.body);
When to switch to HTTPS
The localhost demo is intentionally HTTP. It removes certificate trust from the first check. Once proxy routing works, switch to HTTPS when you need to inspect TLS traffic:
export ROCKXY_PROXY_URL="http://127.0.0.1:8888"
export ROCKXY_CA_BUNDLE="/path/to/RockxyRootCA.pem"
export ROCKXY_TARGET_URL="https://example.com"
node examples/axios-example.js
node examples/got-example.js
For HTTPS inspection, export Rockxy's root certificate and point the Node client at that PEM file. Do not use rejectUnauthorized: false as your normal answer. If TLS fails, fix the CA bundle path, certificate trust, or SSL proxying rule for the host.
A realistic workflow for Node.js API debugging
- Start Rockxy and keep capture enabled.
- Copy the active Rockxy proxy port from the toolbar.
- Run the sample HTTP request to
127.0.0.1:8000/demo. - Confirm Rockxy shows the request before touching your real API.
- Run the same route through the Node client your project uses.
- Move to HTTPS, auth headers, request bodies, retries, worker processes, or test runners only after the simple request is visible.
When the real request is captured, inspect in order: method, host, path, query, auth headers, cookies, body, status, response body, and timing. This catches wrong base URLs, missing tokens, stale cookies, JSON shape mistakes, and backend errors without changing production code paths.
Common mistakes that waste time
Confusing the backend port with the proxy port. In the local demo, 8000 is the demo API and 8888 is Rockxy. The Node client connects to the API through the proxy.
Seeing ECONNREFUSED 127.0.0.1:9090. That means the script is still using an old proxy port. Export ROCKXY_PROXY_URL with the active toolbar port and rerun the client.
Getting status=200 but no Rockxy flow. The client probably reached the target directly, or another process owns the proxy port. Use explicit proxy options and check Rockxy's capture state.
Depending only on environment proxy support. Newer Node versions have more built-in proxy behavior, but packages do not all behave the same way. For debugging, explicit options are easier to review and reproduce.
Disabling TLS verification too early. Certificate bypasses can hide the exact trust issue you need to fix. Prefer an exported Rockxy CA bundle and client-specific certificate options.
Make the first captured request boring
The best Node.js debugging session starts with a boring request: local host, known path, known status, visible in Rockxy. After that, your real bug has fewer places to hide.
If you are setting this up for a team, start with the Node.js setup guide and the sample repo. The same capture workflow applies whether the request comes from a CLI, an Express route, a Next.js server action, a Vitest worker, a queue consumer, or a standalone script.
For the underlying HTTPS mechanics, read How to Debug HTTPS Traffic on macOS and How Rockxy Intercepts HTTPS Without Compromising Security. If you also debug mobile clients, the companion article on Flutter API requests on macOS covers simulator, emulator, and physical-device routing.