All skills
clickhouse avatar

/clickhouse-js-node-coding

@faa5b11 official
by clickhouseclickhouse/agent-skills543 stars
39

Write idiomatic application code with the ClickHouse Node.js client (`@clickhouse/client`). Use this skill whenever a user is *building* against the Node.js client — configuring the client, pinging, inserting rows in JSON or raw formats, selecting and parsing results, binding query parameters, managing sessions and temporary tables, working with data types or customizing JSON parsing. Do NOT use for browser/Web client code.

Use this Skill: https://skilld.dev/gh/clickhouse/agent-skills/clickhouse-js-node-coding

This session only. Nothing lands on disk.

referencesessions.md

≈1.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Sessions and Temporary Tables

Applies to: all versions. session_id is a server-level concept; the client just forwards it on every request that names it.

Answer checklist

When answering "temp table disappears between calls" / "how do I share a session" / anything involving session_id:

  • State plainly that temporary tables and session-scoped state are tied to a session_id — without a stable session_id across calls, every request gets a fresh server-side session and the temp table is gone.
  • Set session_id via crypto.randomUUID() either on createClient or per-call.
  • Warn that session_id on a global / module-static client is an anti-pattern in any concurrent app (Express, server actions, workers, etc.) — concurrent requests will share the same session and trip "Session is locked by a concurrent client". Recommend a short-lived per-workflow client or per-call session_id instead.
  • If session_id is set on the client, also set max_open_connections: 1 to serialize calls and avoid the concurrent-session error.
  • For ClickHouse Cloud or any load-balanced deployment: explicitly recommend replica-aware routing or a single-node hostname as the primary remedy when sessions are needed. Sessions are pinned to one node; behind an LB, consecutive requests may land on different nodes and the temp table will appear to vanish. "Just collapse the workflow into one handler" or "use a non-temporary table" are valid fallbacks but secondary — name the routing fix first.

When you need a session

Use a session_id whenever multiple calls must share server-side state:

  • CREATE TEMPORARY TABLE (the table only exists within its session).
  • SET <setting> = <value> to apply for subsequent queries on the same session.
  • Any other server feature scoped per session (e.g., session-scoped variables in newer ClickHouse versions).

⚠️ session_id and concurrency

ClickHouse rejects concurrent queries within the same session — if two requests arrive at the server at the same time sharing the same session_id, the second one gets an error like "Session is locked by a concurrent client". This has two practical implications:

  1. Do not set session_id on a global / module-static client that handles concurrent requests (e.g., an Express app's shared client). Every in-flight request would share the same session and collide under load.
  2. If you do set session_id on a client, restrict its concurrency: set max_open_connections: 1 so at most one request is in flight at a time, turning the pool into a serial queue. This is fine for a dedicated per-workflow client but wrong for a shared application client.

The right pattern for application code: create a short-lived client (or use per-request session_id) scoped to a single logical workflow, not to the entire process.

Per-client session_id

Appropriate when one client handles exactly one sequential workflow (a script, a background job, a single user's session that you've already manually serialized in the code).

import { createClient } from "@clickhouse/client";
import * as crypto from "node:crypto";

const client = createClient({
  session_id: crypto.randomUUID(),
  max_open_connections: 1, // safeguard against concurrent-session errors
});

await client.command({
  query: "CREATE TEMPORARY TABLE temporary_example (i Int32)",
});

await client.insert({
  table: "temporary_example",
  values: [{ i: 42 }, { i: 144 }],
  format: "JSONEachRow",
});

const rs = await client.query({
  query: "SELECT * FROM temporary_example",
  format: "JSONEachRow",
});
console.info(await rs.json());
await client.close();

Session-level SET commands

SET only persists within a session. With session_id defined on the client, every subsequent call inherits the change.

import { createClient } from "@clickhouse/client";
import * as crypto from "node:crypto";

const client = createClient({
  session_id: crypto.randomUUID(),
  max_open_connections: 1, // safe-guard against concurrent-session errors
});

await client.command({
  query: "SET output_format_json_quote_64bit_integers = 0",
  clickhouse_settings: { wait_end_of_query: 1 }, // ack before next call
});

const rs1 = await client.query({
  query: "SELECT toInt64(42)",
  format: "JSONEachRow",
});
// → 64-bit integers come back as numbers in this query

await client.command({
  query: "SET output_format_json_quote_64bit_integers = 1",
  clickhouse_settings: { wait_end_of_query: 1 },
});

const rs2 = await client.query({
  query: "SELECT toInt64(144)",
  format: "JSONEachRow",
});
// → 64-bit integers come back as strings again

await client.close();

wait_end_of_query: 1 matters here. Without it, a SET on one connection in the pool may not yet be applied when the next query lands on the same socket.

Per-request session_id

You can also pass session_id on a single query() / insert() / command() call to override (or set) it for that one request.

⚠️ Sessions and load balancers / ClickHouse Cloud

Sessions are bound to a specific ClickHouse node. If a load balancer in front of ClickHouse routes consecutive requests to different nodes, the temporary table / SET won't be visible — you'll get UNKNOWN_TABLE / surprising results.

Mitigations (in order of preference):

  • For ClickHouse Cloud, use replica-aware routing so consecutive requests in the same session land on the same node. This is the right primary fix when you need sessions in a Cloud deployment.
  • Talk to a single node directly (e.g., a node-pinned hostname) when routing isn't an option.
  • As a fallback only: avoid sessions for cross-node workflows and persist intermediate state in a regular (non-temporary) table instead. This trades the session requirement away rather than fixing it — use it only if replica-aware routing / single-node connections aren't available.

Common pitfalls

  • Forgetting session_id and being surprised that CREATE TEMPORARY TABLE "disappears." Without a session, every request may land on a different connection / server context.
  • Setting session_id on a shared application client. Under concurrent load, two in-flight requests will share the same session and one will fail with "Session is locked by a concurrent client". Use per-request session_id or a dedicated short-lived client instead.
  • Reusing the same session_id across unrelated workflows. A second session-using consumer will trip over your temporary tables and SET values. Generate a fresh UUID per logical session.
  • Leaving session state pinned for the lifetime of the process. If long-lived clients accumulate SET / temp-table state, consider creating a short-lived sub-client with its own session_id for the unit of work.
  • Skipping wait_end_of_query: 1 on SET — race conditions between SET and the next query can show up under load.

Source: SKILL.md on GitHub

No alerts3mo3 checks · Risk SAFE
  • Gen Agent Trust Hub3mo

    This skill provides safe and idiomatic instructions for using the ClickHouse Node.js client. It emphasizes security best practices, particularly regarding SQL injection prevention through mandatory query parameterization.

  • Socket3mo

    No alerts

  • Snyk3mo

    Risk: LOW · No issues

Signed by skilld at faa5b11. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 3 months ago
  • Database
  • clickhouse
  • nodejs
  • javascript
  • client
  • sql
  • insert
  • query
  • orm

README badge

README badge for clickhouse/agent-skills/clickhouse-js-node-coding

Writes idiomatic Node.js code against the ClickHouse client (`@clickhouse/client`), covering client configuration, inserts in JSON formats, parameterized queries, result parsing, sessions, and modern data types. Applies only to Node.js runtimes, not browser environments or Next.js Edge.

Generated from the current SKILL.md.

Does this skill cover browser or Next.js Edge runtime code?
No. This skill is for Node.js only, including Next.js Node runtime API routes and Server Actions. For browser, Web Workers, Next.js Edge, or Cloudflare Workers, use `@clickhouse/client-web` instead.
Which insert format should I use by default?
Prefer `JSONEachRow` with `values: [...]` unless your scenario requires a different format like raw CSV, TSV, or Parquet.
How do I safely bind user-supplied values in queries?
Always use ClickHouse's native `query_params` with `{name: Type}` syntax — never template-literal-interpolate values into SQL, as this is a SQL injection risk.
Can I override `clickhouse_settings` on individual calls?
Yes. Settings passed to `createClient` are defaults for all requests, but you can override them per-call by passing `clickhouse_settings` directly to `insert()`, `query()`, or `command()`.
Do I need to explicitly close the client?
Yes, call `await client.close()` when the client is no longer needed or during graceful shutdown for global resources.

Generated from the current SKILL.md. These answers refresh after source changes.