Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.helix-db.com/llms.txt

Use this file to discover all available pages before exploring further.

For the complete documentation index optimized for AI agents, see llms.txt.
Helix Cloud exposes a single query surface over HTTP. Every query — whether a deep graph traversal, a vector search, a bulk write, or all three at once — is expressed in the same composable DSL and executes as one serializable transaction. Queries are authored once in TypeScript or Rust (or by hand as JSON) and shipped to the runtime in one of two shapes: dynamic or stored. The Querying Guide walks the DSL end-to-end; this page covers the choice between the two deployment shapes and how each one looks on the wire.

Installing the SDKs

The TypeScript and Rust examples below are built with the Helix SDKs. Install the one for your language to follow along — both emit the same JSON, so the bundle is interchangeable.
npm install @helix-db/helix-db
For the full project layout and generator setup, see TypeScript Project Setup and Rust Project Setup.

The two ways to ship a query

Dynamic queryStored query
Where the query livesInside each request bodyqueries.json, deployed to the cluster
HTTP endpointPOST /v1/queryPOST /v1/query/<name>
Request bodyFull envelope: request_type, query, parameters, parameter_typesJust the parameter object
Per-request overheadHigher — the inline query is deserialized on every callLowest — the route is pre-registered
When to reach for itAd-hoc queries, prototyping, internal tools, anything you do not want to deploy ahead of timeStable production traffic and any route you call repeatedly
The query itself — the JSON AST under query or read_routes.<name> — is identical between the two modes. The same DSL produces it, and the same engine runs it. Only the deployment path changes.

Dynamic queries

A dynamic query is sent inline: the request body carries the JSON AST that a stored route would hold, plus a small envelope that names the request kind and any runtime parameters. There is no deployment step. This is the right shape for one-off queries, exploratory work, and any code path where you do not want to maintain a registered route. The example below counts every User node. The TypeScript and Rust tabs are two builder front-ends that produce the same JSON envelope; pick whichever your codebase already speaks. The JSON tab shows the resulting envelope verbatim — if you want to skip the DSL and POST hand-written JSON, this is the body you send.
import { g, readBatch } from "@helixdb/enterprise-ql";

const countUsers = readBatch()
  .varAs("user_count", g().nWithLabel("User").count())
  .returning(["user_count"]);

// Full POST body for /v1/query.
const body = countUsers.toDynamicJson();
Sending that envelope is one POST to /v1/query. The response is a JSON object keyed by the names from .returning([...]).
const response = await fetch("https://helix.example.com/v1/query", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body, // from .toDynamicJson() above
});

const { user_count } = await response.json();
For larger envelopes, write the JSON to a file and use curl --data-binary @file.json — the inline --data-raw form gets unwieldy past a few steps.

Stored queries

A stored query is registered into queries.json at build time and deployed to the cluster through the control plane. Clients then invoke it by name; the request body carries only the parameters, and the gateway routes straight to the pre-compiled procedure — no inline-query deserialization on the hot path. The example below takes the same countUsers query from the previous section and registers it as a count_users route. The TypeScript and Rust tabs wrap the builder code with the registration helpers (registerRead + defineQueries in TS, #[register] in Rust) and emit queries.json. The JSON tab shows the resulting bundle entry — the inner read_routes.count_users value is exactly the same shape as the query field of the dynamic envelope above.
import {
  defineQueries,
  g,
  readBatch,
  registerRead,
} from "@helixdb/enterprise-ql";

function countUsers() {
  return readBatch()
    .varAs("user_count", g().nWithLabel("User").count())
    .returning(["user_count"]);
}

export const queries = defineQueries({
  read: { count_users: registerRead(countUsers) },
});

// Build-time: emit queries.json for the runtime to load.
await queries.generate("queries.json");
Once queries.json is deployed, callers invoke the route by name. The body is just the parameter object — {} here, since count_users takes none. The response shape matches the dynamic case — keyed by the names in .returning([...]).
const response = await fetch(
  "https://helix.example.com/v1/query/count_users",
  {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({}),
  },
);

const { user_count } = await response.json();
For the full deployment workflow — parameters, bundles, the typed queries.call.* helpers — see Parameters & bundles.

Transactions

Every query — stored or dynamic, read or write — executes as a single transaction with serializable snapshot isolation.
  • Serializable. Transactions behave as if they executed one at a time, even when running concurrently.
  • Snapshot isolation. Each transaction reads from a consistent point-in-time snapshot. Reads within a transaction are never affected by concurrent writes.
  • Automatic. Transactions are implicit. Every query invocation is a transaction. There is no manual BEGIN / COMMIT / ROLLBACK.
Read-only queries never block writes. Write transactions are serialized through the single writer process for correctness without distributed locking. See Guarantees for the full consistency and durability contract.

Next Steps

Querying Guide

Tutorial walkthrough of the DSL with TypeScript, Rust, and JSON for every example.

Parameters & bundles

defineParams, stored bundles, dynamic requests, and the typed call helpers in depth.

Working with HelixDB

Deploy-time and runtime workflow, query warming, and HTTP semantics.

Data Model

Nodes, edges, properties, and the labeled multigraph model.