> ## 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.

# TypeScript

> How to setup a TypeScript project to use HelixDB

> For the complete documentation index optimized for AI agents, see [llms.txt](/llms.txt).

Queries for HelixDB can also be authored in TypeScript using the
[`@helix-db/helix-db`](https://www.npmjs.com/package/@helix-db/helix-db) package.
The TypeScript DSL builds the same JSON AST as the Rust [`helix-db`](/database/rust-project-setup)
crate, so the resulting `queries.json` bundle is interchangeable. Use TypeScript when your service
is already Node.js or you want end-to-end type inference on parameters.

For the conceptual model and per-builder walkthrough, see the
[Querying Guide](/database/querying-guide/overview).

## Prerequisites

* Node.js 20 or later.
* A package manager (`npm`, `pnpm`, or `yarn`).
* Optional: the [Helix CLI](/cli/getting-started) for deploying the resulting bundle.

## Create a new project

```bash theme={"languages":{"custom":["languages/helixql.json"]}}
mkdir helix-queries
cd helix-queries
npm init -y
npm pkg set type=module
```

`@helix-db/helix-db` is published as an ES module, so the `"type": "module"` field
in `package.json` matters. Skip this step if you are adding queries to an existing
ESM-configured project.

## Add the dependency

```bash theme={"languages":{"custom":["languages/helixql.json"]}}
npm install @helix-db/helix-db
npm install --save-dev typescript @types/node tsx
```

`tsx` is optional but convenient for running the generator without a separate build step.

## Minimal `tsconfig.json`

```json theme={"languages":{"custom":["languages/helixql.json"]}}
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src/**/*.ts"]
}
```

The DSL relies on strict mode for the typestate-tracked traversal builder; turn it on.

## Author your queries

Queries are plain functions that return a `ReadBatch` or `WriteBatch`. To bundle them,
declare their parameters with `defineParams`, wrap them with
`registerRead` / `registerWrite`, and collect them with `defineQueries`. The bundle's
`generate(path)` method writes the same `queries.json` shape that the Rust
[`generate_to_path`](/database/rust-project-setup#generate-the-bundle) emits.

```ts src/queries.ts theme={"languages":{"custom":["languages/helixql.json"]}}
import {
  NodeRef,
  Order,
  Predicate,
  PropertyInput,
  PropertyProjection,
  defineParams,
  defineQueries,
  g,
  param,
  readBatch,
  registerRead,
  registerWrite,
  writeBatch,
} from "@helix-db/helix-db";

// Parameters for the read route.
const userByUsernameParams = defineParams({
  username: param.string(),
});

function userByUsername(p = userByUsernameParams) {
  return readBatch()
    .varAs(
      "user",
      g()
        .nWithLabel("User")
        .where(Predicate.eqParam("username", "username"))
        .project([
          PropertyProjection.renamed("$id", "id"),
          PropertyProjection.new("username"),
          PropertyProjection.new("tier"),
        ]),
    )
    .returning(["user"]);
}

// Parameters for the write route.
const addUserParams = defineParams({
  username: param.string(),
  tier: param.string(),
});

function addUser(p = addUserParams) {
  return writeBatch()
    .varAs(
      "newUser",
      g()
        .addN("User", {
          username: PropertyInput.param("username"),
          tier: PropertyInput.param("tier"),
        })
        .project([PropertyProjection.renamed("$id", "id")]),
    )
    .returning(["newUser"]);
}

export const queries = defineQueries({
  read: {
    user_by_username: registerRead(userByUsername, userByUsernameParams),
  },
  write: {
    add_user: registerWrite(addUser, addUserParams),
  },
});
```

A few things to know about this snippet:

* The keys under `read` and `write` name each query in the bundle and back the typed
  `queries.call.*` helpers (see below). Names must be unique across reads *and* writes — a
  duplicate throws `GenerateError` at bundle time.
* `PropertyInput.param("name")` feeds a parameter into an `addN` property bag or a
  `setProperty`; for predicates use `Predicate.eqParam(...)` and friends.

## Add a generator entry point

```ts src/generate.ts theme={"languages":{"custom":["languages/helixql.json"]}}
import { queries } from "./queries.js";

await queries.generate("queries.json");
console.log("generated queries.json");
```

`queries.generate(path)` writes the bundle and resolves to the path it wrote. The file
is laid out identically to the Rust bundle so the runtime treats them as equivalent.

## Wire up the script

```json theme={"languages":{"custom":["languages/helixql.json"]}}
{
  "scripts": {
    "generate": "tsx src/generate.ts"
  }
}
```

```bash theme={"languages":{"custom":["languages/helixql.json"]}}
npm run generate
```

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

* Mount it into the local `enterprise-dev` runtime as `PATH_TO_QUERIES` — see
  [Local Development](/database/local-development).
* Deploy it to HelixDB through the control plane — see
  [Working with HelixDB](/database/working-with-enterprise).

## Sending registered queries from TypeScript

`defineQueries` also exposes a typed `call` map. Each entry takes the query's
parameter values (inferred from `defineParams`) and returns a `DynamicQueryRequest`
you hand to the client's `.dynamic(...)`:

```ts theme={"languages":{"custom":["languages/helixql.json"]}}
import { Client } from "@helix-db/helix-db";
import { queries } from "./queries.js";

const client = new Client("https://your-helix.example.com")
  .withApiKey("hx_secret"); // omit for a local instance

const { user } = await client
  .query<{ user: unknown }>()
  .dynamic(queries.call.user_by_username({ username: "alice" }))
  .send();
```

`call.<name>` is fully typed — an unknown key or wrong parameter type is a compile error — and
sets `query_name` to the route key so logs show `user_by_username`, not `__dynamic__`. See
[Querying](/database/querying) for the full client send path.

## Next Steps

<CardGroup cols={2}>
  <Card title="Querying Guide" icon="book-open" href="/database/querying-guide/overview">
    Tutorial-style walkthrough of the DSL, from the simplest read to parameter-bound bundles.
  </Card>

  <Card title="Working with HelixDB" icon="rocket" href="/database/working-with-enterprise">
    Deploy `queries.json` and the runtime workflow.
  </Card>

  <Card title="Local Development" icon="laptop-code" href="/database/local-development">
    Run the bundle locally with the `enterprise-dev` image.
  </Card>

  <Card title="npm package" icon="npm" href="https://www.npmjs.com/package/@helix-db/helix-db">
    `@helix-db/helix-db` on npm.
  </Card>
</CardGroup>
