chDB Node.js bindings — an in-process ClickHouse engine for Node, Bun and Deno.
v3 (Layer 1) is in development. The v2
query/queryBind/SessionAPI is preserved (your v2 code keeps working); v3 adds async queries, server-side parameter binding, inserts, streaming, and Arrow output.
npm i chdbPrebuilt native binaries ship as per-platform subpackages (@chdb/lib-*,
resolved via optionalDependencies) — no local compilation, no node-gyp, no
Python. First-batch platforms: Linux x64/arm64 (glibc) and macOS x64/arm64.
Windows is not supported (use WSL2).
const{ query, queryAsync, insert, Session }=require("chdb");// or: import { ... } from "chdb"// Sync standalone query (v2-compatible, returns a string)console.log(query("SELECT version(), 'Hello chDB'","CSV"));// Async query (non-blocking) -> ChdbResult (text() / json() / bytes() + metrics)constr=awaitqueryAsync("SELECT number FROM numbers(5)",{format: "JSONEachRow"});console.log(r.rowsRead,r.elapsed);// Server-side parameter binding (no SQL injection surface)const{ queryBind }=require("chdb");console.log(queryBind("SELECT {n:UInt32} * 2 AS v",{n: 21},"CSV"));// 42// Session: persistent/in-memory databaseconstsession=newSession();// temp dir; or new Session("./data")session.query("CREATE TABLE t (id UInt32, name String) ENGINE = MergeTree() ORDER BY id");// Insert (inline, async; never reads stdin)awaitsession.insert({table: "t",values: [{id: 1,name: "Alice"},{id: 2,name: "Bob"}]});// Streaming (chunk-by-chunk, no full buffering)forawait(constrowofsession.queryStream("SELECT * FROM t").rows()){console.log(row);}// Arrow output (no serialization on your side)consta=awaitsession.queryAsync("SELECT * FROM t",{format: "arrow"});consttable=a.toArrow();// requires the optional `apache-arrow` peer dep// const bytes = a.bytes(); // raw Arrow IPC if you bring your own Arrowsession.close();// (cleanup() is an alias; `using` is supported too)Errors are typed (ChdbSyntaxError, ChdbQueryError, ChdbConnectionError,
ChdbBindError, ChdbInsertError, ChdbStreamError, ChdbArrowError,
ChdbAbortError, ChdbTimeoutError, …), each carrying .code, the ClickHouse
.clickhouseCode, and .cause.
libchdb binds a single data directory per process, so opening a Session takes
the slot the stateless query/queryAsync calls were using and closes their
connection. A connection closed while an operation is still running on it aborts
the engine for the rest of the process, so new Session() refuses instead:
const{ queryAsync, Session, drainPending }=require("chdb");constp=queryAsync("SELECT max(sipHash64(number)) FROM numbers(20000000)");newSession();// throws: 1 standalone operation is still runningawaitp;newSession();// fineAwaiting your own promise is not always enough. An aborted or timed-out call
rejects immediately while the engine keeps computing, and close() returns
before the connection is really gone when an operation is still using it.
drainPending() waits for both:
constac=newAbortController();constp=queryAsync("SELECT max(sipHash64(number)) FROM numbers(20000000)",{signal: ac.signal,});ac.abort();awaitp.catch(()=>{});// rejected, but the engine is still computingawaitdrainPending();// now the connection is actually freeconsts=newSession("./data");Moving between directories works the same way: after session.close(), wait with
drainPending() before opening one at a different path. Opening another session
at the same path needs no wait — those connections coexist by design.
Behaviour change. Earlier versions did not refuse — they closed the busy connection, which usually aborted the engine and on macOS could leave a query whose promise never settled. Code that opened a session without awaiting its standalone queries now gets an error at the call site instead of a failure somewhere later.
| Capability | Status |
|---|---|
| Stateless query (sync + async) | ✅ |
| Session (persistent / in-memory) | ✅ |
Server-side parameter binding ({name:Type}) | ✅ |
| Insert (object / positional rows) | ✅ |
Streaming results (AsyncIterable) | ✅ |
Arrow output (format: 'arrow' + toArrow()) | ✅ |
| AbortSignal / timeout | ✅ (single-shot is honest: rejects early; native runs to completion) |
Arrow scan (registerArrowTable, Arrow input) | ⏳ follow-up |
Arrow zero-copy (M2, { zeroCopy: true }) | ⏳ follow-up |
chDB ↔ @clickhouse/client integration (chdb/connection, experimental) | ✅ |
Status: this integration uses the experimental
createClient({ connection })hook in@clickhouse/client(clickhouse-js#879 merged; framing follow-up #880 merged). Upstream considers this a deliberately narrow chDB-only hook — not a public plugin system — and the shape may change. We'll keepchdb/connectionworking against whatever the upstream hook evolves into.
For users coming from @clickhouse/client, chdb-node ships a
Connection implementation under the chdb/connection subpath that
plugs into @clickhouse/client's createClient({ connection })
injection point (tracking issue:
clickhouse-js#865).
import{createChdbConnection}from'chdb/connection'constconn=createChdbConnection({path: ':memory:'})constr=awaitconn.query({query: 'SELECT * FROM numbers(5)',format: 'JSONEachRow'})letbody=''forawait(constchunkofr.stream)body+=Buffer.from(chunk).toString('utf8')console.log(JSON.parse(`[${body.trim().split('\n').join(',')}]`))awaitconn.close()// chDB-specific escape hatches (raw ChdbResult, raw insert, session info)conn.chdb.queryAsync('SELECT 1',{format: 'arrow'})// bytes/text/json/toArrowconn.chdb.session.path// bound on-disk pathSee docs/design/pluggable-connection.md
for the full design, the Connection interface, the .chdb extension
namespace, the tests/clickhouse-js/skip_list.json parity blacklist,
and the sync policy with @clickhouse/client.
- Layered API design: the Layer 1 / Layer 2 / Layer 3 architecture, package shape, and intended user-facing surfaces.
- Layer 1 native binding reviewer guide: the PR #43 design and implementation map, organized by commit and review feedback.
- chDB ↔
@clickhouse/clientintegration (experimental): thechdb/connectionsurface, theConnectioninterface chdb-node implements, the.chdbextension namespace, and the parity-test sync policy.
A single N-API binary serves Node 18/20/22 + Bun + Deno.
npm install # JS deps only (no compile-on-install)
npm run libchdb # download libchdb for this platform
npm run build # node-gyp build + fix loader path + tsc (dist)
npm run test:all # v2 (mocha) + v3 (vitest)
npm run build:platform # package this platform's @chdb/lib-* subpackage