TL;DR
コードの全量はGitHubを参照ください。
下記コマンドで試せます。
git clone https://github.com/mineco13/duckdb-node-neo
cd duckdb-node-neo
docker compose down&&docker compose up -d --build
open http://localhost:3000
はじめに
duckdbにNode.js clientがあるらしく試してみようと思ったのですが、従来のClientはDeprecatedとなっていたので新しいものを使いました。
古いクライアントも軽く試してみましたがcallback styleしか対応してなかったので書き味は少し悪かったです。
新しいクライアントはPromiseスタイルの書き方に対応するなど機能が追加されてますが、一部未機能の機能があったりと開発途中のようです。
今回はS3に見立てたMinIO上にあるcsvファイルをRemix上でテーブル形式にして表示してみます。
前提条件
下記インストール済みの想定です
- Docker
- Node.js
準備
表示用のcsvデータを用意
適当なcsvファイルを用意します。
name,github_star,url
Material-UI (MUI),90500,https://mui.com/
Chakra UI,34700,https://chakra-ui.com/
Ant Design,87000,https://ant.design/
React Bootstrap,22000,https://react-bootstrap.github.io/
Semantic UI React,13100,https://react.semantic-ui.com/
Blueprint,20000,https://blueprintjs.com/docs/
Grommet,8100,https://v2.grommet.io/
Evergreen,12000,https://evergreen.segment.com/
Rebass,7900,https://rebassjs.org/
Fluent UI,15000,https://developer.microsoft.com/en-us/fluentui
※人気のReact Component libraryをGitHub star数とともにAIに出力してもらいました。
MinIOコンテナ(S3互換)を用意
先ほど作ったdata/sample.csv
を自動で読み込むminioコンテナを、下記のようにcompose.yaml
に定義します
services:
minio:
image: minio/minio:latest
ports:
- 9000:9000
- 9001:9001
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server /export --console-address ":9001"
createbuckets:
image: minio/mc
depends_on:
- minio
entrypoint: >
/bin/sh -c "
until (/usr/bin/mc config host add myminio http://minio:9000 minio minio123) do echo '...waiting...' && sleep 1; done;
/usr/bin/mc mb myminio/mybucket;
/usr/bin/mc policy download myminio/mybucket;
/usr/bin/mc cp /data/sample.csv myminio/mybucket/sample.csv;
exit 0;
"
volumes:
- ./data:/data
下記でMinIOにブラウザでアクセスして、compose.yaml
内のMINIO_ACCESS_KEY
とMINIO_SECRET_KEY
を入力してログインすることでバケットとその中身を確認できます
open http://localhost:9000
Remixテンプレート作成
画面表示用のテンプレートとしてRemixを使っていきます。(Next.jsみたいなものです)
npx create-remix@latest remix
localhost:5173
にアクセスしてそれっぽい画面が表示されれば完了です
cd remix
npm run dev
## ACCESS http://localhost:5173
開発
npm i @duckdb/node-api
duckdbライブラリを用いて先ほど定義したMinIOからデータを読み出して、整形して返します。
columnNames()
やawait result.getRows()
など、結果に容易にアクセスできるネイティブのメソッドが用意されているのは嬉しいですね。
import { DuckDBInstance } from "@duckdb/node-api";
export const fetchCSV = async () => {
const instance = await DuckDBInstance.create();
const con = await instance.connect();
await con.run(
`
INSTALL httpfs;
LOAD httpfs;
`
);
await con.run(
`
CREATE SECRET secret1 (
TYPE S3,
KEY_ID '${process.env.S3_ACCESS_KEY_ID}',
SECRET '${process.env.S3_SECRET_ACCESS_KEY}',
REGION 'us-east-1',
ENDPOINT '${process.env.S3_HOST}:${process.env.S3_PORT}',
USE_SSL false,
URL_STYLE 'path'
);
`
);
const result = await con.run(
"SELECT * FROM read_csv_auto('s3://mybucket/sample.csv');"
);
return { header: result.columnNames(), rows: await result.getRows() };
};
Remixからはloader
(getServerSideProps
的な)経由で呼び出してテーブル形式で出力します。
fetchCSV
の結果をjsonにそのまま詰めて返したい所ですが、TypeError: Do not know how to serialize a BigInt
を吐いて止まるので、bigintをstringに変換するロジックを挟んでいます。
import { useLoaderData } from "@remix-run/react";
import { fetchCSV } from "~/duckdb";
...
export const loader = async () =>
json(
JSON.parse(
JSON.stringify(await fetchCSV(), (key, value) =>
typeof value === "bigint" ? value.toString() : value
)
)
);
export default function Index() {
const data = useLoaderData<typeof loader>();
return (
...
</div>
<table>
<caption>Data from Object Storage</caption>
<tr>
{data.header.map((header) => (
<th scope="col">{header}</th>
))}
</tr>
{data.rows.map((row, index) => (
<tr key={index}>
{row.map((d) => (
<td scope="col">{d}</td>
))}
</tr>
))}
</table>
</header>
...
下記スクリプトで起動してテーブルが出力されることを確認します。
S3_HOST=localhost S3_PORT="9000" S3_ACCESS_KEY_ID=minio S3_SECRET_ACCESS_KEY=minio123 npm run dev
これでひとまず完了です。
コンテナ化
蛇足ですが、下記のようなremix/Dockerfile
を作成して
FROM node:20-slim
WORKDIR /remix
COPY package*json .
RUN npm install
COPY . .
RUN npm run build
CMD ["npm", "run", "start"]
compose.yamlに書き加えたらコンテナ化まで完了です。
services:
remix:
build:
context: remix
ports: [3000:3000]
environment:
S3_HOST: minio
S3_PORT: "9000"
S3_ACCESS_KEY_ID: minio
S3_SECRET_ACCESS_KEY: minio123
depends_on:
- createbuckets
...
起動コマンドです。
docker compose down&&docker compose up -d --build
open http://localhost:3000
感想
様々な形式のデータにSQLインターフェースでアクセスできるのは楽しいです。
積極的に使っていこうと思います。