Skip to main content
For the complete documentation index optimized for AI agents, see llms.txt.
Queries for HelixDB can be authored directly in Go with the github.com/helixdb/helix-db/sdks/go module. The Go SDK is dynamic-first: write ordinary Go functions that return helix.Request, declare parameters inline, and execute with client.Exec(ctx, request, &out). There is no bundle-generation step in the primary Go workflow. For the traversal model and query patterns themselves, see Querying and the Querying Guide.

Prerequisites

  • Go 1.22 or later.
  • Optional: the Helix CLI for local development and ad-hoc query testing.

Create a project

mkdir helix-go-app
cd helix-go-app
go mod init example.com/helix-go-app

Add the dependency

go get github.com/helixdb/helix-db/sdks/go
Import the SDK as helix:
import helix "github.com/helixdb/helix-db/sdks/go"

Write query functions

Go queries are normal functions. Use helix.ReadQuery("name") or helix.WriteQuery("name") to set the dynamic request’s query_name, then declare runtime parameters inline with methods such as q.ParamString, q.ParamI64, and q.ParamDateTime.
package users

import helix "github.com/helixdb/helix-db/sdks/go"

type UserRow struct {
	ID       int64  `json:"$id"`
	Name     string `json:"name"`
	TenantID string `json:"tenantId"`
}

type FindUsersResponse struct {
	Users []UserRow `json:"users"`
}

func FindUsers(tenantID string, limit int64) helix.Request {
	q := helix.ReadQuery("find_users")

	tenant := q.ParamString("tenant_id", tenantID)
	maxRows := q.ParamI64("limit", limit)

	return q.
		VarAs("users",
			helix.G().
				NWithLabel("User").
				Where(helix.PredEq("tenantId", tenant)).
				Limit(maxRows).
				ValueMap("$id", "name", "tenantId"),
		).
		Returning("users")
}
Parameter refs can be passed directly into predicates, stream bounds, and mutation property inputs. The SDK inserts both parameters and parameter_types into the dynamic envelope.

Parameterize request-specific values

Direct Go values are serialized as literals in the query AST. This is useful for true constants, but it is not a runtime parameter:
helix.G().NWhere(helix.SourceEq("id", "foo")) // embeds "foo" in the AST
For values that change per request, declare a q.Param* value and pass the returned ParamRef. That keeps the query shape stable and lets the server reuse cached work across requests:
q := helix.ReadQuery("user_by_id")
id := q.ParamString("id", userID)

return q.
	VarAs("user", helix.G().NWhere(helix.SourceEq("id", id)).ValueMap("$id", "name")).
	Returning("user")
The same rule applies to traversal predicates such as helix.PredEq, bounds such as Limit, mutation properties, and search inputs: pass a ParamRef for request-specific values, not a direct literal.

Return explicit variables

The names passed to Returning(...) define the top-level response object keys and should match your response struct tags:
type FindUsersResponse struct {
	Users []UserRow `json:"users"`
}

return q.VarAs("users", traversal).Returning("users")
Use zero-argument Returning() only when the response is intentionally empty, such as an idempotent index-creation request. The SDK serializes it as "returns":[].

Execute queries

package users

import (
	"context"

	helix "github.com/helixdb/helix-db/sdks/go"
)

func ListUsers(ctx context.Context, client *helix.Client, tenantID string, limit int64) (FindUsersResponse, error) {
	var out FindUsersResponse
	err := client.Exec(ctx, FindUsers(tenantID, limit), &out)
	return out, err
}
Create the client once and reuse it:
client, err := helix.NewClient("https://helix.example.com", helix.WithAPIKey("hx_secret"))
if err != nil {
	return err
}

var out FindUsersResponse
err = client.Exec(ctx, FindUsers("acme", 25), &out)
For local development, pass "" or "http://localhost:6969" to NewClient.

Write queries

type CreateUserResponse struct {
	User []UserRow `json:"user"`
}

func CreateUser(name string, tenantID string) helix.Request {
	q := helix.WriteQuery("create_user")

	nameParam := q.ParamString("name", name)
	tenant := q.ParamString("tenant_id", tenantID)

	return q.
		VarAs("user",
			helix.G().AddN("User", helix.Props{
				helix.Prop("name", nameParam),
				helix.Prop("tenantId", tenant),
			}),
		).
		Returning("user")
}
Use execution options when a write must hit the writer node or wait for durability:
var created CreateUserResponse
err = client.Exec(ctx, CreateUser("Alice", "acme"), &created,
	helix.WriterOnly(),
	helix.AwaitDurability(true),
)

Handle conflicts in application code

Client.Exec does not retry HTTP 409 Conflict responses automatically. Retry only when your operation is safe to replay. Remote errors are returned as *helix.HelixError with StatusCode set, and conflicts wrap helix.ErrConflict. Use helix.IsConflict(err) or errors.Is(err, helix.ErrConflict) instead of parsing the error text.
func ExecWithConflictRetry(ctx context.Context, client *helix.Client, build func() helix.Request, out any) error {
	for attempt := 0; attempt < 3; attempt++ {
		err := client.Exec(ctx, build(), out)
		if err == nil || !helix.IsConflict(err) || attempt == 2 {
			return err
		}
		time.Sleep(time.Duration(attempt+1) * 50 * time.Millisecond)
	}
	return nil
}

Next Steps

Querying

Dynamic query envelopes, client execution, and transactions.

Parameters & bundles

Parameter serialization across TypeScript, Rust, Go, and Python.

Local Development

Run HelixDB locally while developing queries.

Go SDK source

Go module source and README.