duckdb-api

June 27, 2026 ยท View on GitHub

Run DuckDB behind a Hono HTTP API in a Node.js Docker container.

This project uses @duckdb/node-api and currently targets DuckDB v1.5.4.

Usage

Install dependencies and run the API locally:

npm install
npm run dev

Build and run the Docker image:

npm run build:docker
npm run dev:docker

The API listens on port 3000 by default.

Docker Extensions

npm run build:docker runs scripts/download_extensions.sh before building the image. The script downloads Linux AMD64 DuckDB v1.5.4 extension binaries for:

  • httpfs
  • iceberg
  • ducklake
  • nanoarrow

The app loads httpfs, iceberg, and nanoarrow during initialization.

Environment Variables

  • PORT: Port to run the API on. Defaults to 3000.
  • API_TOKEN: Bearer token for POST /query.
  • USERNAME: Username for basic auth. Requires PASSWORD.
  • PASSWORD: Password for basic auth. Requires USERNAME.

Basic auth applies to both POST /query and POST /streaming-query when USERNAME and PASSWORD are set.

AWS S3 access:

  • AWS_REGION: AWS region for S3 access.
  • AWS_ACCESS_KEY_ID: AWS access key ID for S3 access.
  • AWS_SECRET_ACCESS_KEY: AWS secret access key for S3 access.

Cloudflare R2 Data Catalog / Iceberg access:

  • R2_CATALOG: Catalog name.
  • R2_ENDPOINT: Catalog endpoint URI.
  • R2_TOKEN: Cloudflare API token for R2.

API

Both query endpoints accept a JSON request body with a query property:

{
  "query": "SELECT 1 AS value;"
}

User-submitted queries are filtered to block duckdb_settings, INSTALL, LOAD, SET, PRAGMA, and SECRET usage.

POST /query

Executes a SQL query and returns JSON row objects.

Response content type:

application/json

Example:

curl -X POST http://localhost:3000/query \
  -H "Content-Type: application/json" \
  -d '{"query": "SELECT 1 AS value;"}'

Example response:

[
  {
    "value": 1
  }
]

POST /streaming-query

Executes a SQL query with DuckDB's streaming result API and streams newline-delimited JSON rows.

Response content type:

application/x-ndjson

Example:

curl --location http://localhost:3000/streaming-query \
  --header "Content-Type: application/json" \
  --data '{
    "query": "SELECT * FROM '\''https://shell.duckdb.org/data/tpch/0_01/parquet/orders.parquet'\'' LIMIT 100"
  }'

Example response:

{"o_orderkey":1,"o_custkey":36901}
{"o_orderkey":2,"o_custkey":78002}

@duckdb/node-api does not expose the legacy arrowIPCStream() API from the old duckdb package, so this endpoint streams NDJSON instead of Arrow IPC bytes.