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.
Stored queries for HelixDB are authored in Rust using the helix-enterprise-ql crate (imported as helix_dsl). This page covers bootstrapping a Rust project that builds your queries into the queries.json bundle that the runtime consumes. For the traversal model and query patterns themselves, see Querying and the docs.rs reference.

Prerequisites

  • A recent stable Rust toolchain — install via rustup if you don’t already have it.
  • Optional: the Helix CLI for running the resulting bundle against a local instance.

Create a new Rust project

generate_to_path is invoked from main, so a binary crate is the simplest layout:
cargo new helix-queries
cd helix-queries
You can also add this as a binary inside an existing Cargo workspace if you’d rather keep your queries alongside the rest of your service code.

Add the dependency

cargo add helix-enterprise-ql
Or add it directly to Cargo.toml:
[dependencies]
helix-enterprise-ql = "0.1.7"
The crate is published on crates.io as helix-enterprise-ql but its module path is helix_dsl, so all imports look like use helix_dsl::....

Set up src/main.rs

fn main() {
    let path = helix_dsl::generate_to_path("queries.json").expect("generate queries bundle");
    println!("generated {}", path.display());
}
generate_to_path collects every query you’ve defined in the crate and writes a single queries.json bundle to the path you pass. It returns the resolved path so you can log it or hand it to a downstream deploy step.

Importing the DSL

Inside the modules where you author queries, bring the DSL into scope with the prelude:
use helix_dsl::prelude::*;
The prelude re-exports the common building blocks — read_batch, write_batch, the traversal builder g(), sub-traversal sub(), NodeRef/EdgeRef, Predicate/SourcePredicate, the projection helpers (PropertyProjection, ExprProjection), and AggregateFunction. See the docs.rs reference for the full surface and Querying for how the pieces fit together.

Setting up your queries

Queries are defined as top-level pub fn items annotated with #[register]. Each function returns either a WriteBatch (for mutations) or a ReadBatch (for read-only traversals), and generate_to_path picks them up automatically when the bundle is built. The function arguments become the query’s named parameters at the HTTP edge. For a small project you can keep everything in src/main.rs alongside fn main(); once you have more than a handful of queries, move them into their own modules.
use helix_dsl::prelude::*;

// Write query: insert a new User node.
#[register]
pub fn add_user(userId: String, name: String) -> WriteBatch {
    let _ = (&userId, &name);
    write_batch()
        .var_as(
            "newUser",
            g().add_n(
                "User",
                vec![
                    ("userId", PropertyInput::param("userId")),
                    ("name",   PropertyInput::param("name")),
                ],
            )
            .project(vec![PropertyProjection::renamed("$id", "id")]),
        )
        .returning(["newUser"])
}

// Read query: fetch a single User by id.
#[register]
pub fn user_by_id(userId: String) -> ReadBatch {
    let _ = &userId;
    read_batch()
        .var_as(
            "user",
            g().n_with_label("User")
                .where_(Predicate::eq_param("userId", "userId"))
                .project(vec![
                    PropertyProjection::renamed("$id", "id"),
                    PropertyProjection::new("name"),
                ]),
        )
        .returning(["user"])
}

fn main() {
    let path = helix_dsl::generate_to_path("queries.json").expect("generate queries bundle");
    println!("generated {}", path.display());
}
A few things to know about this snippet:
  • #[register] registers the function with the global query bundle so generate_to_path will include it in queries.json. No manual list to maintain.
  • The function name becomes the route name. Once deployed, callers invoke POST /v1/query/add_user with a JSON body like {"userId": "u-1", "name": "Alice"}.
  • The let _ = (&userId, &name); line silences Rust’s unused-variable warning — the macro reads the parameter names at compile time to wire them into the JSON schema, so the values themselves aren’t used in the body.
  • var_as("name", traversal) binds a sub-result, and .returning([...]) chooses which bound names appear in the response payload.
For the full builder catalog — edges, richer predicates, vector and text search, sub-traversals, and aggregations — see the docs.rs reference and Querying.

Generate the bundle

cargo run
This produces queries.json in the project root. From here you can:

Next Steps

Querying

Traversal DSL, stored queries, dynamic queries, and transactions.

Working with HelixDB

Deploy queries.json and the runtime workflow.

Local Development

Run the bundle locally with the enterprise-dev image.

DSL API Reference

Full helix_dsl API on docs.rs.