Your app fires a dozen GraphQL calls to render one screen, and something is wrong — a field is missing, a list is empty, a mutation silently no-ops. You open a network debugger, and every single row says the same thing: POST /graphql 200. Same method, same path, same status. In a generic HTTP view they are indistinguishable, so you cannot tell which operation failed, what variables it sent, or what shape the response came back in.
That is the GraphQL debugging problem in one sentence: the transport hides the operation. The outcome of this guide is the opposite — after a few steps, each /graphql row in Rockxy on macOS is readable as a distinct operation, with its query, its variables, and its JSON response laid out so you can see exactly what one call asked for and got back.
Why every GraphQL call looks the same
REST spreads meaning across the URL and the verb: GET /users/42 and DELETE /orders/7 tell you what happened before you open anything. GraphQL does not. Almost every request is a POST to a single endpoint — usually /graphql — with a JSON body like this:
{
"operationName": "GetOrder",
"query": "query GetOrder($id: ID!) { order(id: $id) { id total status } }",
"variables": { "id": "7" }
}
The operation name, the selected fields, and the arguments all live inside the request body. A network view that only reads the URL and status line has nothing to distinguish one call from the next. And because a successful GraphQL response returns HTTP 200 even when the payload carries an errors array, the status column lies to you too: a broken query and a healthy one look identical from the outside.
So debugging GraphQL is really about reading the body — the query, the variables, and the response JSON — per operation. That is exactly what a proxy debugger is built to do, as long as it treats GraphQL-over-HTTP as its own thing rather than an opaque POST.
What you will need
- Rockxy on macOS 14 (Sonoma) or later. It is a native app; download it from the Rockxy download page.
- A client that talks to a GraphQL endpoint over HTTP — a browser app, a mobile client on the same network, or a script hitting
/graphql. - The client routed through Rockxy so its traffic is captured. For a local service on
127.0.0.1, see the note on loopback below. - For HTTPS endpoints (most of them): the host inside Rockxy's SSL Proxying scope with certificate trust in place, so the JSON body is decrypted rather than tunneled.
One framing to keep straight from the start: Rockxy inspects GraphQL over HTTP. It reads, filters, and replays the operations your client already sends. It is not a standalone GraphQL query client or schema explorer — the traffic has to flow through the proxy for Rockxy to show it.
Step 1 — Get the GraphQL traffic into Rockxy
Start Rockxy, confirm capture is on, and drive your client so it issues at least one GraphQL call. If the endpoint is remote, routing is the usual proxy setup. If it is a local dev server on 127.0.0.1, loopback traffic skips the proxy by default — route the client explicitly and clear the no-proxy list, which the localhost capture guide walks through in full.
What you should see in Rockxy: one or more rows for your GraphQL host, each a POST to the /graphql path. If the endpoint is HTTPS and you see a tunnel row with no readable body, decryption did not happen — add the host to SSL Proxying scope and confirm certificate trust before continuing. Without a decrypted body there is no query or variables to read.
Step 2 — Read one operation in the GraphQL previewer
Select a /graphql row. Rockxy detects GraphQL-over-HTTP and offers a GraphQL previewer tab for the flow, so instead of a wall of escaped JSON in a single query string, you get the operation broken into its parts: the operation name, the query document itself, the variables that were bound to it, and the JSON response — including any errors array the server returned alongside data.
This is where the earlier lie unravels. A 200 row whose body contains "errors" is now obviously a failure; a mutation that returned null for the field you cared about is now obviously a no-op. You are reading the operation, not the transport.
POST /graphql becomes a specific, named call you can reason about.Step 3 — Isolate the operation you care about
When one screen fires a dozen operations, scrolling for the right row is the slow path. Use Rockxy's filters and search to narrow the list to the operation you are chasing. Because the operation name and fields live in the request body, body-aware search matters here — you can search across request content, and JSONPath queries let you match on structure rather than a raw substring. To pin down an operation by name, a JSONPath such as:
$.operationName
lets you filter on the field that names each call, so GetOrder stops hiding among ten other /graphql rows. You can save a filter you use often so the next debugging session starts already scoped. The exact query grammar and where saved filters live are covered in the filters and search docs; the capability you want is "match a request by a field inside its JSON body," and JSONPath is how you express it.
Step 4 — Re-run one operation with edited variables
Reading a failed operation usually raises a follow-up: what if the variable were different? Rather than rebuild the whole client state to fire the call again, re-run the captured operation directly. Rockxy's Replay re-sends a flow as-is, and Compose lets you edit the request first — method, URL, headers, query, and body — before sending. For GraphQL that means changing a value in the variables object, or adjusting the query document, and sending just that one operation to see how the response changes.
That turns a vague "the order query returns nothing" into a tight loop: replay GetOrder with id: "7", then with id: "8", and compare the two responses side by side. Rockxy's Diff Compare makes that comparison explicit — the response diff guide covers the workflow for lining up two payloads and reading only what changed.
Going further: stub and intercept GraphQL responses
Once you can read and replay an operation, two more tools help when the server is not cooperating or you want to test a shape the backend cannot yet produce.
Map Local serves a canned response from a local file in place of the real one. For GraphQL, that means returning a JSON payload of your choosing for a matching request — handy for reproducing a specific data or errors shape without touching the backend, or for pinning a flaky response while you fix the client.
Breakpoints pause a matching request or response in flight so you can edit it before it continues. Pausing on the way out lets you change the query or variables the server actually receives; pausing on the way back lets you rewrite the response the client actually parses. Together they let you answer "what would the client do if the server sent this?" without waiting on a server change. The exact rule-matching UI for Map Local and Breakpoints is in the Rockxy docs — link out from the app rather than guessing at menu labels.
If it does not work
The row is a tunnel with no readable body. The endpoint is HTTPS and the flow was not decrypted. Add the host to SSL Proxying scope and confirm the client trusts Rockxy's certificate. Until the body is decrypted, there is no query or variables to inspect.
The GraphQL previewer does not appear for a row. Rockxy detects GraphQL from the HTTP request shape. If a client sends the operation in an unusual encoding, or the body is not decrypted, detection has nothing to work with. Confirm the body is readable first, then re-select the row.
A pinned host will not decrypt. If the GraphQL client pins its certificate, Rockxy does not silently defeat that. The pinned or failed handshake surfaces as a failed transaction in the request list, so you can see the attempt happened rather than being handed a decrypted body that was never really available.
You expected one row but see several — or one giant one. Some clients send batched queries, where multiple operations travel inside a single HTTP request body. In that case the operations appear within one flow's body rather than as separate rows. Read the batched array in the request body to see each operation the client bundled together.
Search does not match your operation. Confirm you are searching request content, not just the URL, and that your JSONPath points at a field that actually exists in the body — operationName is only present when the client sends it. If the client omits it, filter on a distinctive field inside the query or variables instead.
What Rockxy does and does not do with GraphQL
To set expectations honestly: Rockxy inspects GraphQL as it travels over HTTP(S). It detects the operation, decodes the query and variables, filters and searches the body, replays and edits a captured call, and stubs or intercepts the response. All of that is grounded in the HTTP flow you captured.
It is not a schema-aware GraphQL IDE. It does not introspect your schema, autocomplete fields, or validate a query against types — that is what your GraphQL client or playground is for. And HTTPS GraphQL still depends on the same TLS rules as any other traffic: the host must be in SSL Proxying scope with certificate trust, and pinned hosts will surface as failed transactions rather than decrypting. Keep GraphQL framed as detection and inspection over HTTP, and the tool does exactly what it claims.
Next steps
The core move is small: stop reading the POST /graphql status line and start reading the body. Rockxy's GraphQL previewer gives you the query, variables, and response per operation; filters and search — including JSONPath — isolate the one call you care about; and Replay, Compose, Map Local, and Breakpoints let you re-run and reshape it.
From here, get local GraphQL traffic captured with the localhost capture guide, wire a real service into the proxy with the Node.js API debugging guide, and line up two operation responses with the response diff guide. The full filter grammar lives in the filters and search docs.