EY-Office ブログ

ブラウザー上で動くPGliteをReactから使ってみた

あなたは、MySQL派ですか?、PostgreSQL派ですか?
私が最初に使ったデータベースはOracle v5ですが、独立してからはPostgreSQLをメインで使ってきました。
今まで本格的なデータベースというとサーバーで起動し、TCP/IP等で接続して使うイメージでした。

しかし最近、Postgres WASMをTypeScript/JavaScriptから呼び出せるようにしたPGliteがリリースされました。PGliteは、Node.jsやBunそしてWebブラウザ上で実行可能です!!

PGlite

いつものジャンケンアプリに組み込んでみた

早速いつものジャンケンReactアプリに組み込んでみました。

PGLiteのインストールは npm install @electric-sql/pgliteでインストールできます。コードはTauriのブログとほぼ同じです。

変更があったのは、メインのJyankenコンポーネントのみで以下のようになっています。

  • ① PostgreSQLとの接続
    • idb://・・・を指定する事で、ストレージにはブラウザーのIndexedDBを使います
  • ② ジャンケンの結果を挿入するSQL文の実行
    • 当然ですがプレースフォルダーが使えます
  • ③ scoresテーブル作成のSQL文の実行
    • IF NOT EXISTS があるのでテーブルが存在しない場合のみ作成されます
  • ④ scoresテーブルから全データを取得するSQL文の実行
    • 取得されるデータの型ScoreTypeをテンプレートで指定しています
    • 取得されたScoreType型の配列がresult.rowsに入っています
"use client";
import { useEffect, useState } from 'react';
import { PGlite } from '@electric-sql/pglite';

const db = new PGlite('idb://my-pgdata');                           // ← ①

export default  function Jyanken () {
  const [scores, setScores] = useState<ScoreType[]>([]);

  const pon = async (human: Te) => {
    const computer = Math.floor(Math.random() * 3) as Te;
    const judgment = (computer - human + 3) % 3 as Te;
    const score: ScoreType = {human, computer, judgment};
    await db.query(                                                 // ← ②
      `INSERT INTO scores (human, computer, judgment) VALUES ($1, $2, $3)`,
      [human, computer, judgment]);
    setScores([score, ...scores]);
  }

  useEffect(() => {
    (async() => {
      await db.exec(                                               // ← ③
        `CREATE TABLE IF NOT EXISTS scores (
          id SERIAL PRIMARY KEY,
          human INTEGER NOT NULL,
          computer INTEGER NOT NULL,
          judgment INTEGER NOT NULL,
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)`);
      const result = await db.query<ScoreType>(                    // ← ④
        `SELECT human, computer, judgment from scores ORDER BY id DESC`
      );
      setScores(result.rows);
    })();
  }, []);

  return (
    <div className="md:ml-8">
      <h1 className="my-4 ml-4 text-3xl font-bold">じゃんけん ポン!</h1>
      <div className="p-3 md:p-6 bg-white md:w-3/5">
        <JyankenBox pon={pon} />
        <ScoreBox scores={scores} />
      </div>
    </div>
  );
}

これだけです。簡単ですね!
ブラウザーを再起動しても、開発サーバーを再起動してもデータは残っています。

IndexedDBの中身

下の画像はIndexedDBのデータファイルの一部ですが、通常のサーバーで動いている/var/lib/postgresql/data/の下にあるファイルのパス名がIndexedDBのキーになっています。対応するIndexedDBの値がサーバーのファイルに対応していると思われます。
IndexedDBには仕様上の容量制限はないようですが、このブログによるとPCでは10GBくらいまで保存できるようです。

PGliteの動作原理ですが、ホームページのHow it worksによると

PostgreSQLは通常プロセスのフォークモデルを使用して動作します。クライアントが接続を開始すると、その接続を管理するために新しいプロセスがフォークされます。しかし、C to WebAssembly (WASM)コンパイラであるEmscriptenでコンパイルされたプログラムは新しいプロセスをフォークすることができず、厳密にシングルプロセスモードで動作します。その結果、PostgreSQLをWASMに直接コンパイルして従来の動作をさせることはできません。幸いなことに、PostgreSQLには、起動時や復旧時のコマンドライン使用を主目的とした “シングルユーザモード “があります。この機能を基に、PGliteはJavaScript環境内でWASMにコンパイルされたPostgreSQLとの対話を容易にする入出力経路を導入しています。 DeepLによる翻訳です

psqlコマンドが欲しいぞ

今回の使い方は単純なので直ぐ動きましたが、本格的なアプリでは多数のテーブルがあり、途中でテーブルの値を確認たり変更したくなりますよね。
その場合は、PGlite REPL React Componentを使えば良いようです。

npm install @electric-sql/pglite-replでインストールし、ここではnext.jsを使っているのでapp/psql/page.tsxファイルを作成し、以下のコードを書きました。

"use client";
import { PGlite } from "@electric-sql/pglite";
import { Repl } from "@electric-sql/pglite-repl";

export default function SqlTool() {
  const pg = new PGlite('idb://my-pgdata');

  return (
    <>
      <h2>PGlite SQL tool</h2>
      <Repl pg={pg} />
    </>
  );
}

http://localhost:3000/psqlをアクセスすると下の画像のようにpsqlのようにSQL文の実行やメタコマンドの実行できます。めでたしめでたし 😁

まとめ

PGliteを使うとブラウザー上でちゃんとしたPostgreSQLが動きます!

今までサーバー・クライアントで動かしていたWebアプリを、ブラウザー(+HTML/CSS, JavaSciptを配信するWebサーバー)で動くアプリに変換できるので新たなタイプのアプリが作れそうですね。

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ