Electronでアプリ作成3　ElectronでSqlite3を操作

　Node.js環境でブラウザベースのアプリが作成できるElectronですが、結構メジャーなアプリがElectronベースで作られているようでね。そんなElectornで取り扱いが簡単なデータベースのSqlite3が操作できたら、いろいろ便利だろうと思い、サンプルとなるアプリを作成しましのたで、投稿します。自分の知識不足で、結構ハマってしまった個所もありましが、とりあえずはデータの挿入、抽出は確認できています。

 # 動作確認環境
 Windows11
 node.js v18.18.1
 electron v27.0.0
 sqlite3 3.43.0

 # 参考になる記事、サイト等
Electronアプリの作成については過去の記事で触れてもらっています。

 https://qiita.com/watmot/items/e79a3f3f8fe27fa54a33
 
 https://qiita.com/watmot/items/5241c97350f3431bd09c

WindowsにSQLite3をインストールしています。こちらのサイト参考にさせてもらいました。大変丁寧でわかりやすかったです。

https://www.kkaneko.jp/tools/win/sqlite3.html


 データ表示をgrid.jsにさせています。以下参考にどうそ。
 
 https://qiita.com/watmot/items/df92e50c891cccdcb68b

# 準備
[Electronアプリを作成できる](https://qiita.com/watmot/items/e79a3f3f8fe27fa54a33)よう準備しておきます。

また、sqlite3をインストールしておきます。

```
npm install -S sqlite3
```

ただし、exeのビルドの際にpakage.jsonのdependenciesにsqlite3が載っていないと実行時にエラーがでてしまいました。手動で修正しました。

``` pakage.json
  "dependencies": {
    "electron-squirrel-startup": "^1.0.0",
    "sqlite3": "^5.1.6"
  }
```
# コードについて
・preload.hs
・index.js
・main.js
の３つのファイルを編集しています。以下コードを掲載しておきます。

## preload.js
``` preload.js
// レンダラーからNode.jsにアクセスするためのファイル
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('dbOp', {
    createDb: async () => ipcRenderer.invoke('createDb'),    //データベース作成
    selectAll: async () => ipcRenderer.invoke('selectAll'),  //SELECT *
    insertData: async (memoText) => ipcRenderer.invoke('insertData', memoText), //データを挿入
});
```
## main.js
``` main.js
// main.jsはNode.jsにて動作する requireにてelectronモジュールを読み込んでいる
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const sqlite3 = require('sqlite3');
const db = new sqlite3.Database("./my_database.db");

// ウィンドウの作成準備
const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,   //ウィンドウサイズ
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'), //プレローダーの指定
    }
  });
  // 読み込むファイル
  win.loadFile('index.html');
  // デベロッパーツールを開く　不要ならコメントアウト
  // win.webContents.openDevTools();
  // メニューバーの非表示
  win.setMenuBarVisibility(false);
}

// ウィンドウ初期化時
app.whenReady().then(() => {
  // テーブル作成　第2引数asyncでうまく戻り値拾えず。ちゃんとnew Promiseで記述しています。
  ipcMain.handle('createDb', (eve) =>
    new Promise((resolve, reject) => {
      db.run('CREATE TABLE IF NOT EXISTS my_memo ([id] integer primary key autoincrement, [memo] text, [date_time] datetime);', err => {
        if (err) reject(err);
        resolve();
      });
    }));

  // SELECT文でのデータ取得
  ipcMain.handle('selectAll', (eve) =>
    new Promise((resolev, reject) => {
      db.serialize(() => {
        db.all('SELECT * FROM my_memo', (err, rows) => {
          if (err) reject(err);
          resolev(rows);
        });
      });
    })
  );

  // データ挿入
  ipcMain.handle('insertData', (eve, memoText) =>
    new Promise((resolev, reject) => {
      db.run('INSERT INTO my_memo (memo, date_time) VALUES (? , datetime("now", "localtime"));', memoText, err => {
        if (err) reject(err);
        resolev();
      });
    })
  )

  // ウィンドウの表示
  createWindow();
  // macOS用
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  })
})

// ウィンドウのクローズ
app.on('window-all-closed', () => {
  db.close();
  if (process.platform !== 'darwin') app.quit();
})

```

## index.html
``` index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset='UTF-8' />
  <title>ElectronでSqlite3</title>
  <!-- データ表示にGrid.jsを利用 -->
  <link rel='stylesheet' href='https://unpkg.com/gridjs/dist/theme/mermaid.min.css'>
  <script src='https://unpkg.com/gridjs/dist/gridjs.umd.js'></script>
</head>

<body>
  <h1>ElectronでSqlite</h1>
  <p>テキストをSqlite3にて記録するサンプルです</p>
  <div>
    <input type='text' id="memoInput">
    <input type='button' id='insertBtn' value='書込み'>
  </div>
  <div id='gridTag'></div>

  <script>
    // テーブル作成　即時関数として呼出し
    window.dbOp.createDb()
      .catch(err => { console.log(err) });

    // 起動時にメモをgridに表示
    let grid; // Grid.jsオブジェクト
    window.dbOp.selectAll()
      .then(ret => {
        ret.forEach((elem) => { delete elem.id });  //キー:idを削除
        grid = new gridjs.Grid({
          data: ret
        }).render(document.getElementById('gridTag'));
      })
      .catch(err => { console.log(err) });

    // ボタンクリック時
    document.getElementById('insertBtn').onclick = () => {
      // メモを書き込む
      const memoText = document.getElementById('memoInput').value;
      if (memoText) {
        window.dbOp.insertData(memoText)
          .catch(err => { console.log(err) });
      }
      document.getElementById('memoInput').value = "";

      // メモをgridに表示
      window.dbOp.selectAll()
        .then(ret => {
          ret.forEach((elem) => { delete elem.id });  //キー:idを削除
          grid.updateConfig({
            data: ret
          }).forceRender();
        })
        .catch(err => { console.log(err) });
    }
  </script>
</body>

</html>
```

テキストボックスに記入した文字がデータベースに保存されます。アプリ起動時と、ボタン押下時に、SELECT文を実行して、更新しています。データベースファイルははプロジェクトフォルダまたは、実行ファイルと同じフォルダに自動で作成されますが、絶対パスで指定しておくこともできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536745/098d58fc-5dbb-87ef-f7dd-3a6465b7c74d.png)

以上ご参考まで。