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.

referenceasync-insert.md

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

Async Inserts

Applies to: all client versions; the relevant settings are server-side. See https://clickhouse.com/docs/en/optimize/asynchronous-inserts.

When to use async inserts: when many small inserts arrive concurrently (e.g., one per HTTP request) and you don't want to maintain a client-side batching layer. ClickHouse will batch them server-side. This is also the recommended ingestion pattern for ClickHouse Cloud.

When not to use async inserts: when you already build large batches client-side (e.g., from a stream). Plain inserts are simpler and lower latency.

Setup

Enable on the client level or per-request via clickhouse_settings:

import { createClient, ClickHouseError } from "@clickhouse/client";

const client = createClient({
  url: process.env.CLICKHOUSE_URL,
  password: process.env.CLICKHOUSE_PASSWORD,
  max_open_connections: 10,
  clickhouse_settings: {
    async_insert: 1,
    wait_for_async_insert: 1, // wait for ack from server
    async_insert_max_data_size: "1000000",
    async_insert_busy_timeout_ms: 1000,
  },
});

Concurrent small inserts

Each call still uses the client's normal insert() API — the server merges the batches.

const promises = [...new Array(10)].map(async () => {
  const values = [...new Array(1000).keys()].map(() => ({
    id: Math.floor(Math.random() * 100_000) + 1,
    data: Math.random().toString(36).slice(2),
  }));

  await client
    .insert({ table: "async_insert_example", values, format: "JSONEachRow" })
    .catch((err) => {
      if (err instanceof ClickHouseError) {
        // err.code matches a row in system.errors
        console.error(`ClickHouse error ${err.code}:`, err);
        return;
      }
      console.error("Insert failed:", err);
    });
});

await Promise.all(promises);

wait_for_async_insert — fire-and-forget vs ack

wait_for_async_insert Promise resolves when… Trade-off
1 (default) Server has flushed the batch to the table Slower per call; insert errors surface to the client
0 Server accepted the row into its in-memory buffer Faster; flush errors won't surface — only validation/parsing errors

With wait_for_async_insert: 1, expect each insert call to take roughly async_insert_busy_timeout_ms to resolve when traffic is light, because the server waits for more rows or for the timer to fire before flushing.

Combining DDL with async inserts

When creating tables in scripts that immediately insert, ack the DDL with wait_end_of_query: 1 so the table is ready before the first insert:

await client.command({
  query: `
    CREATE OR REPLACE TABLE async_insert_example (id Int32, data String)
    ENGINE MergeTree ORDER BY id
  `,
  clickhouse_settings: { wait_end_of_query: 1 },
});

Even better is to create a specialized client for inserts with the appropriate async settings and a separate client for DDL and other queries.

Common pitfalls

  • Setting async_insert per call but expecting client-side batching. The client still issues each insert() as a separate HTTP request — the batching happens on the server.
  • Confusing wait_for_async_insert (async-insert ack) with wait_end_of_query (DDL ack). They are unrelated.
  • Treating a resolved insert under wait_for_async_insert: 0 as durably written. It only means the server accepted the bytes; flush failures will not surface to the client.
  • Not handling ClickHouseError. It exposes err.code, which maps to rows in the system.errors table — use it to decide whether to retry.

See also

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.