Node.js SDK
On this page
Use the Node.js SDK from server-side TypeScript or JavaScript applications.
Before you start
The examples use Node.js 20 or later. The SDK also supports Bun and Cloudflare Workers from trusted server-side code. Follow Connect an application, then set the endpoint and API key used by the examples:
export SCOPEDB_ENDPOINT="https://<endpoint>"
export SCOPEDB_API_KEY="<api-key>"Install
npm install scopedbIf you use pnpm, run pnpm add scopedb instead. The package is ESM-only.
Verify the connection
Save this complete example as connect.mjs. The .mjs extension enables native ES modules and top-level await, without a TypeScript runner:
import { Client } from "scopedb";
const endpoint = process.env.SCOPEDB_ENDPOINT;
const apiKey = process.env.SCOPEDB_API_KEY;
if (!endpoint || !apiKey) {
throw new Error("SCOPEDB_ENDPOINT and SCOPEDB_API_KEY are required");
}
const client = new Client(endpoint, { apiKey });
const ready = await client.query("SELECT 1 AS ready");
console.log(JSON.stringify(ready.toObjects({ integerMode: "string" })));Run it from the same shell where you set the connection variables:
node connect.mjsExpected output:
[{"ready":"1"}]Keep the API key on the server. Do not create authenticated ScopeDB clients in browser code.
The SDK does not read scope login credentials. If the CLI works but the SDK does not, check that the application process receives both connection variables. Reuse this client when trying the examples below.
Execute a statement
First create and populate the events table from the ScopeQL quickstart. The connection test above does not require an application table.
const result = await client
.statement(`
FROM events
WHERE service = 'checkout'
ORDER BY time DESC
LIMIT 10
`)
.execute();execute() returns a finished result or throws when execution fails, is
cancelled, or the request is aborted. It handles an active statement's polling
internally.
Work with results
For most application code, convert rows to objects keyed by column name:
const rows = result.toObjects();
console.log(rows[0]);An empty array means the query succeeded but no rows matched; rows[0] is then undefined.
Use first() for a lookup or aggregate that returns at most one row, and
toValues() when positional arrays are more convenient.
Result value mapping
toValues(), toObjects(), and first() convert result cells according
to their ScopeDB result type:
| ScopeDB result type | JavaScript value | Notes |
|---|---|---|
int | bigint | Use integerMode to return a number or decimal string instead. |
uint | bigint | Use integerMode to return a number or decimal string instead. |
float | number | Finite values only; NaN and infinities cause conversion to throw. |
binary | string | Hexadecimal representation. |
string | string | |
boolean | boolean | |
timestamp | Date | JavaScript Date preserves milliseconds, not nanoseconds. |
interval | string | Fixed-duration ISO 8601 representation. |
array | string | JSON text; use JSON.parse() when an array is needed. |
object | string | JSON text; use JSON.parse() when an object is needed. |
any | string | Raw textual value; the SDK does not infer its dynamic JavaScript type. |
null | null | A null cell is null regardless of the field's declared type. |
The null result type can appear in query metadata, such as for a literal
NULL; it is not a table column type. See the
data types reference for ScopeDB type semantics.
ScopeDB integers can exceed JavaScript's safe integer range. The SDK returns
them as bigint by default, which preserves precision but cannot be passed to
JSON.stringify directly. Choose the representation at the application
boundary:
const jsonSafeRows = result.toObjects({ integerMode: "string" });Use:
bigintfor lossless JavaScript arithmetic;stringfor JSON-safe identifiers or unbounded counters;numberonly when values fit within JavaScript's safe integer range.
Converting a timestamp to Date discards sub-millisecond precision. Use
jsonRows() when the application must retain the original result text.
array and object values contain JSON text. Do not assume that an any
value is JSON: it remains raw text because its dynamic ScopeDB type cannot be
reconstructed from the result. When parsing array or object values with
JSON.parse(), nested integers outside JavaScript's safe range can lose
precision. Parsing structured values does not recursively apply the SDK's
result conversions; nested binary, timestamp, and interval values remain
strings.
Control statement execution
Use submit() when you need a StatementHandle instead of waiting through
execute(). The local snapshot does not make a network request; status()
fetches the latest remote state while the statement is active:
const handle = await client.statement("SELECT 1 AS ok").submit();
console.log(handle.lastStatus()?.status);
const latest = await handle.status();
console.log(latest.status);
const result = await handle.wait();An AbortSignal stops client-side requests and polling; it does not cancel a
statement in ScopeDB. Call handle.cancel() to request server-side
cancellation. See the HTTP API for the underlying state
and cancellation contract.
Stream rows to a table
Use table().appendStream() to write rows to a table. The SDK groups rows into
batches automatically. Create a table for this example:
scope query 'CREATE TABLE sdk_example_events (id int, name string)'Run this command once using the ScopeDB CLI with the same endpoint and API key as your application.
const table = client.table("sdk_example_events", {
database: "scopedb",
schema: "public",
});
const stream = table.appendStream().build();
await stream.send({ id: 1, name: "first" });
await stream.send({ id: 2, name: "second" });
await stream.shutdown();send() adds a row to the SDK's buffer. shutdown() sends the remaining rows,
waits for the writes to finish, and closes the stream.
Use flush() when you want to wait for pending writes and then keep using the
same stream.
After shutdown, read the committed rows:
const written = await client.query("FROM sdk_example_events ORDER BY id");
console.log(written.toObjects({ integerMode: "string" }));The result includes IDs "1" and "2" with names "first" and "second".
Each run adds rows to the table. See the
Node.js SDK examples
for bulk writes and logging.
Use ingestStream() when source JSON needs a server-side ScopeQL
transformation before it matches the destination table. Follow
Ingest data for that workflow.
Next steps
- Learn the language with the ScopeQL quickstart.
- Build application queries with Query data.
- Use the ScopeQL reference for exact language syntax.
- Review statement states, errors, and retries in the HTTP API.
- Browse the Node.js SDK repository for the complete package surface.