GraphQL, in a flow
Test GraphQL APIs as files in your own repo.
Bandura is a GraphQL testing tool that runs on your own machine. A
graphql block on a request node sends the query; the node around it
carries the assertions, the captures, the retry policy and the auth. The whole test is
one plain-YAML .aether file that diffs in a pull request and runs
identically in the desktop app, the CLI, and CI. No account, no sync layer, and a
response whose errors array is non-empty fails the test
instead of hiding behind its HTTP 200.
v1.0.0-rc.8 is out now on Windows and Linux. v1.0.0 final, and signed macOS builds, in August 2026.
The mechanism
How to send a GraphQL query: one block on an ordinary request node.
GraphQL is not a separate node type with its own rules. It is a block on the same request node HTTP uses, which is why everything that works for HTTP (capture, assertions, hooks, retry, auth, cookies) works for GraphQL without a second set of features.
- id: get_user
type: request
config:
method: POST
url: ${{ variables.baseUrl }}/graphql
graphql:
query: |
query GetUser($id: ID!) {
user(id: $id) { name email }
}
variables:
id: ${{ variables.userId }}
operationName: GetUser
capture:
userName: response.body.data.user.name
next: verify_user
- id: verify_user
type: assertion
config:
check: 'response.status === 200 && variables.userName.length > 0'
next: null Bandura builds the body
The { query, variables, operationName } JSON body is assembled
for you, and Content-Type: application/json is set unless you
override it. Because the block owns the body, graphql and
body cannot be combined, which removes a whole class of
half-constructed requests.
Variables interpolate like everything else
${{ variables.userId }} works inside GraphQL variables exactly
as it does in a URL or a header, so a captured value from the login step feeds
the query two nodes later. Dynamic values ($uuid(),
$timestamp() and friends) work there too.
Dynamic values →
Captures read under data
capture paths resolve against
response.body.data.…, and the captured value is available to every
later node in the flow, whatever protocol that node speaks.
The part most tools get wrong
HTTP 200 with an errors array is a failure.
GraphQL servers return 200 for execution errors, so a status check alone passes on
a broken query. In Bandura, a response carrying a non-empty errors
array fails the node with the first error's message, by default and without an
assertion. When the errors are the thing under test, set
allowErrors: true and assert on response.body.errors
directly. Either way the choice is written in the file, where a reviewer can see it.
Around the query
The test is everything the request client leaves to you.
Chain GraphQL with everything else
Log in over REST, capture the token, run the mutation, poll a job endpoint until it settles, assert on the result. Conditions, loops, parallel fan-out and subflows are node types in the file, not scripts you maintain. Node reference →
Auth is a block, not a ritual
Basic, bearer, API key, OAuth2 client-credentials and password grants, and AWS SigV4, declared on the flow or on one node. The OAuth2 token is fetched once per run and refreshed before it expires. Cookies are captured and replayed automatically. Authentication →
A debugger over the flow
Breakpoints, step, run-one-node, run-from-node, and a variables panel that names where each value came from. When a GraphQL response surprises you, stop on the node and read it rather than adding print statements. Run and debug →
Offline, and no account
The engine runs on your machine against anything you can reach, including localhost. Flows, settings and run history are files and a local SQLite database on your disk. Where everything sits →
Automate in CI
The same file, headless, on every push.
The CLI is a compiled binary with the engine inside, so CI runs exactly what you ran locally. macOS can run the CLI today, ahead of the signed desktop build.
# The same file, headless. Exit code 1 on any failure.
npm install -g @bandura/cli
bandura run flows/ --reporter junit --reporter github
Deterministic exit codes, JUnit XML, GitHub annotations on the pull request, a
concurrency flag, and run --repeat with p95/p99 thresholds for a latency
gate. API tests in CI →
Honest limits
What this page is not claiming.
The GraphQL block is YAML-only in the editor
The request node has a structured panel for URL, method, headers, params and
retry, but the graphql block itself is edited as YAML in the code
editor. There is no introspection-driven query builder and no schema explorer.
If that is the feature you are choosing a tool for, the established clients have
it and Bandura today does not.
Subscriptions are WebSocket work
There is no dedicated GraphQL subscriptions feature. A
websocket node can open the socket, negotiate a subprotocol such as
graphql-ws, send a subscribe message and collect frames to assert
on, which covers the testing case, but it is protocol plumbing you write
yourself rather than a first-class subscription client.
Questions, answered straight
FAQ
Is Bandura a GraphQL client or a GraphQL testing tool?
Both jobs, one mechanism. A `graphql` block on a request node sends the query, and the same node carries assertions, capture, retry, hooks and auth, so a single .aether file is the request and the test of it. The file is plain YAML in your own Git repo, the graph canvas and the code editor are two renderings of it, and one local engine runs it in the desktop app, the CLI, and CI. There is no account and it works offline against anything you can reach.
How does Bandura handle GraphQL errors that come back as HTTP 200?
It fails the node, which is the behaviour most tools get wrong by default. GraphQL servers return HTTP 200 for execution errors, so a status assertion alone passes on a response whose errors array says the query broke. If the response carries a non-empty errors array, the node fails with the first error's message. When you want to assert on the errors themselves, set allowErrors: true in the block and read response.body.errors like any other value.
Can I mix GraphQL, REST and gRPC calls in one flow?
Yes, and this is where a flow format earns its keep. GraphQL is a block on the ordinary request node, so a flow can log in over REST, capture the token, run a GraphQL mutation with it, then check a side effect over gRPC or wait on a WebSocket message, with conditions, loops and parallel fan-out between the steps. Nothing changes shape between protocols: every step stores { status, headers, body } to assert on and capture from.
Does Bandura have a GraphQL query builder or schema explorer?
No, and this page will not pretend otherwise. The graphql block is written as YAML in the code editor: the query is a YAML block scalar, variables are a map, and there is no structured panel, no introspection-driven autocomplete, and no schema browser. If a point-and-click query builder is the feature you are choosing a tool for, Postman, Insomnia and Bruno all have one and Bandura today does not. What Bandura adds instead is everything around the query: assertions, capture, chaining, a debugger, and CI.
Can I import GraphQL requests from Bruno, Postman or Insomnia?
Partially, and the edges are named rather than fudged. A Bruno request with a body:graphql block imports as a real graphql request node, query and variables intact. Postman and Insomnia GraphQL bodies are currently skipped rather than half-imported, because a silently mangled query is worse than a named gap: the rest of the collection comes across and the GraphQL requests are listed as skipped in the review step.
How do GraphQL tests run in CI?
The same .aether file, headless. The CLI installs with npm install -g @bandura/cli and ships as a compiled binary with the engine inside, so bandura run flows/ executes exactly what the desktop app executes: deterministic exit codes, JUnit XML for the dashboard, GitHub annotations on the pull request, a concurrency flag, and run --repeat with p95/p99 thresholds when a latency gate is wanted.
What does Bandura cost for GraphQL testing?
The download is the complete app, free indefinitely for personal projects, learning, and open source. Commercial use takes a one-time per-seat licence after a 30-day evaluation that disables nothing; the price is announced when checkout opens at launch. It covers every release built in the following 12 months, with a perpetual fallback: when the window ends, the build you have keeps working, permanently, and nothing auto-renews.
How do GraphQL variables work in a flow?
As a variables map in the graphql block, so the query stays a clean document instead of a string you concatenate values into. Everything in the map interpolates with ${{ }} exactly as a URL or header does: a captured id from the login step, an env.* value, or a dynamic helper such as $uuid(). Bandura assembles the { query, variables, operationName } JSON body for you, and captures read the result under response.body.data, so the value one query returns feeds the next one's variables.
Keep reading
The rest of the case.
gRPC, WebSocket and SSE testing
When the suite has to cover the gRPC service behind the GraphQL gateway too: same file format, same assert-and-capture machinery.
Tests as files in your repo
A query change shows up as a reviewable diff in the same pull request as the resolver change it tests.
Run the suite in CI
A schema change that breaks a query fails the build, with the server's own error in the GitHub annotation.
Everything that works offline
Test a localhost GraphQL server with no network at all, and know exactly where flows, tokens and history sit on disk.
Let an agent run your tests
An agent that edits a query can run the flow that exercises it, locally, over a bundled stdio MCP server.
Coming from Postman?
REST requests and environments import; Postman GraphQL bodies are skipped and named, and the guide explains both halves.
v1.0.0-rc.8 is out now. v1.0.0 final in August 2026
Point it at your GraphQL API this afternoon.
Write one query in one file, run it, and read the failure when the
errors array comes back. The 30-day evaluation disables nothing, and
the file stays yours either way. Windows and Linux builds are
up now; on a Mac, leave an address and you get the signed build the day it is
notarized.
Prefer the exact fields first? The
node reference documents the graphql block beside every other node type.