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.
This guide is a tutorial walkthrough of the HelixDB query language. Each page builds on the last, starting from the simplest possible read and ending at parameter-bound stored bundles. Every example shows the TypeScript, Rust, and JSON form side by side, so you can pick a client and follow along — or skim across all three to see how the surfaces relate. The three forms are not three different query languages; they are three encodings of the same query AST. The TypeScript and Rust DSLs are typed builders that emit the JSON shape directly. If you copy the JSON column into a POST /v1/query body, it will execute the same query as the DSL above it.

Installing the SDKs

To follow along in TypeScript or Rust, install the SDK for that language. Both emit the same query AST, so you can switch between them — or use the JSON column directly and skip the SDK entirely.
npm install @helix-db/helix-db
For the full project layout and generator setup, see TypeScript Project Setup and Rust Project Setup.

The running example

Every page in this guide uses one small social-graph schema, so each new step plugs into a domain you already know.
  • Nodes: User (username, tier, createdAt), Post (title, body, embedding, createdAt), Tag (name).
  • Edges: FOLLOWS (User → User), AUTHORED (User → Post), LIKED (User → Post), TAGGED (Post → Tag).
  • Indexes: a unique-equality index on User.username, a vector index on Post.embedding, a text index on Post.body.
Index setup itself is covered on the Search page; for now assume they exist.

How a query is shaped

Every query — read or write — is a small pipeline:
  1. Open a batch with readBatch() or writeBatch().
  2. Bind one or more named sub-results with .varAs("name", traversal). A traversal always starts from g() and chains steps until it produces nodes, edges, or a terminal value.
  3. Choose which bound names to surface to the caller with .returning([...]).
The TypeScript shell:
readBatch()
  .varAs("name", g().someSource().someStep())
  .varAs("other", g().someSource().someStep())
  .returning(["name", "other"]);
The Rust shell is identical except for naming conventions (read_batch, var_as, returning). The JSON shell is what the batch serializes to: a top-level {queries: [...], returns: [...]} object, with each varAs producing one {Query: {name, steps, condition}} entry. The Reading nodes page covers the shell step by step; this overview just shows the finished product.

A full first example

Fetch a user by username and the ten most recent posts they authored. This example takes no parameters; it hard-codes "alice" and a limit of 10 so the JSON column is unambiguous. Parameter binding is introduced on the Filtering and Parameters & bundles pages.
import {
  g,
  NodeRef,
  Order,
  PropertyProjection,
  SourcePredicate,
  readBatch,
} from "@helixdb/enterprise-ql";

const aliceFeed = readBatch()
  .varAs("user", g().nWhere(SourcePredicate.eq("username", "alice")))
  .varAs(
    "posts",
    g()
      .n(NodeRef.var("user"))
      .out("AUTHORED")
      .orderBy("createdAt", Order.Desc)
      .limit(10)
      .project([
        PropertyProjection.renamed("$id", "post_id"),
        PropertyProjection.new("title"),
        PropertyProjection.new("createdAt"),
      ]),
  )
  .returning(["user", "posts"]);

const body = aliceFeed.toDynamicJson(); // POST body for /v1/query
Two things to notice:
  • The posts traversal starts from NodeRef.var("user") — that’s how one named binding feeds into the next. The same name appears as {"N": {"Var": "user"}} in the JSON.
  • The TypeScript and Rust DSLs surface a friendly nWithLabel / n_with_label alias, but this example uses nWhere(SourcePredicate.eq("username", "alice")) directly because the read is anchored on a unique property, not a label scan. The next page covers when to use each anchor.

Sending the request

The JSON above is the actual body for POST /v1/query. Below are the same request in three transports — TypeScript using fetch, Rust using reqwest, and curl straight from the shell. All three are interchangeable; pick whichever fits your environment.
// Continuing from the snippet above.
const response = await fetch("https://helix.example.com/v1/query", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: aliceFeed.toDynamicJson(),
});

if (!response.ok) {
  throw new Error(`Helix returned ${response.status}`);
}

const { user, posts } = await response.json();
The response is a JSON object keyed by the names from .returning([...]) — here { "user": [...], "posts": [...] }. Each value is the list of rows produced by that named binding. If this query were deployed as a stored route under the name alice_feed, the client would hit POST /v1/query/alice_feed with a much smaller body (just the parameters object — empty {} here, since this query takes none). The Parameters and bundles page walks through both shapes.

How to read this guide

Each page follows the same template:
  1. A short prose intro to the concept.
  2. One or more <CodeGroup> blocks. The TypeScript tab is the lead — every example is written there first.
  3. The Rust tab mirrors the TypeScript exactly, with snake-case method names and the trailing-underscore convention for Rust keywords (where_, in_, as_).
  4. The JSON tab is the serialized AST that either DSL produces. It is the body you would POST to /v1/query for a dynamic request; the same object appears inside queries.json under read_routes.<name> or write_routes.<name> for a stored route.
You can also jump straight to any topic from the list below; the pages do not assume strict sequential reading, but anything earlier in the list is fair game later on.

Next Steps

Reading nodes and edges

Source anchors: n by id, nWithLabel, nWhere, and the edge equivalents.

Traversals

Walking the graph: out, in, both, edge variants, multi-hop chains.

Filtering, ordering, paging

Predicates, ordering, and slicing the result stream.

Projections

Shaping the output: values, valueMap, project, aggregations.

Mutations

writeBatch, addN, addE, setProperty, drop.

Vector and text search

vectorSearchNodes, textSearchNodes, follow-on traversal from hits.

Advanced patterns

repeat, union, choose, coalesce, optional, varAsIf, forEachParam.

Parameters and bundles

defineParams, stored bundles, dynamic requests, typed call helpers.