LoginSignup
0
0

フロントjsでも使えそうなデータ保存方法

Last updated at Posted at 2024-06-02

はじめに

趣味でプログラミングをする際にフロントjsを使うことが多いのですが、
フロントjsのみでデータを保存することができるのか気になったので調べました。

※なんだかんだ言って面倒だからGPTにコード生成してもらった。

使用ライブラリに関しては下記記事のデータストレージの項目から選びました。

自分が使いたいもの(メモ)

どれか一つを選ぶとしたらDexie.jsを使おうと思っています。

正直、indexedDBを使用していてフロントjsで使えるものであればなんでもよく、
その中からDexie.jsを選んだ理由は他のフロントjsで使用できるライブラリと比べて性能が
どうこうというよりただの好き嫌いで選んでいます。

Dexie.jsはLocalForageやPouchdbと比べて更新や削除処理に違和感がないです。
LocalForageやPouchdbはデータ削除するのにget(テーブル)やgetItem(テーブル)が
必要になります。

Dexie.jsもdb.テーブル.delete()という風に実行するのでテーブルにアクセスしていることに変わりはないですが、個人的に削除といっているのにget()が記載されているのに違和感がありました。

またDexie.jsのデータ更新系メソッドはSQLと同じメソッド名なのでしっくりきます。

今まで上げた内容はlovefieldも当てはまるのですが、
両方を調べてみた感じ参考記事の量はDexie.jsのが多そうでした。

Dexie.js

// データの削除
function deleteData(id) {
    db.friends.delete(id).then(() => {
        console.log("Data deleted from IndexedDB");
        displayData(); // 再表示
    }).catch(error => {
        console.error("Error deleting data:", error);
    });
}

LocalForage

// データの削除
function deleteData(id) {
    db.getItem('friends').then(friends => {
        friends.splice(id, 1);
        return db.setItem('friends', friends);
    }).then(() => {
        console.log("Data deleted from LocalForage");
        displayData();
    }).catch(error => {
        console.error("Error deleting data:", error);
    });
}

Pouchdb

// データの削除
function deleteData(id) {
    db.get(id).then(doc => {
        return db.remove(doc);
    }).then(() => {
        console.log("Data deleted from PouchDB");
        displayData(); // 再表示
    }).catch(error => {
        console.error("Error deleting data:", error);
    });
}

lovefield

// データの削除
function deleteData(id) {
    todoDb.delete().from(item).where(item.id.eq(id)).exec().then(() => {
        console.log("Data deleted from IndexedDB");
        displayData(); // 再表示
    }).catch(error => {
        console.error("Error deleting data:", error);
    });
}

フロントjsで使えるもの

Dexie.js

IndexedDBのラッパー

indexedDB
フロントjsでデータ保存ができればいいだけなら
これだけでもOK。そのままだと使いにくいらしい。
https://zenn.dev/awyaki/scraps/06a41ea81f55cf
https://qiita.com/Akihiro0711/items/8d68c8bd3cec33708e7a

LocalForage

下記ストレージが使えるみたいです。

  • IndexedDB
  • Web SQL(W3Cにより非推奨)
  • localStorage(セキュアではない)

Pouchdb

IndexedDBを使用
オフライン時にデータを貯蓄しオンライン時に同期できるみたいです。
(同期機能は使わない)

Lovefield

IndexedDBのラッパー
SQLのようにIndexedDBを操作できます。

コード

実行して画面に表示されたボタンをポチポチしてもらうと
データが保存されているのを確認できると思います。

Dexie.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dexie Example</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dexie/4.0.4/dexie.min.js"></script>
</head>
<body>
  <button onclick="addData()">Add Data to IndexedDB</button>
  <button onclick="reloadPage()">Reload Page</button>

  <ul id="dataList"></ul>

  <script>
      // Dexieインスタンスの作成
      const db = new Dexie("myDatabase");
      
      // データベースのテーブル定義
      db.version(1).stores({
          friends: '++id,name,age'
      });

      // データの登録
      function addData() {
          db.friends.add({name: "Alice", age: 30}).then(() => {
              console.log("Data added to IndexedDB");
              displayData();
          }).catch(error => {
              console.error("Error adding data:", error);
          });
      }

      // データの参照と表示
      function displayData() {
          const dataList = document.getElementById("dataList");
          dataList.innerHTML = ""; // Clear previous data

          db.friends.toArray().then(friends => {
              friends.forEach(friend => {
                  const listItem = document.createElement("li");
                  listItem.textContent = `${friend.name}, ${friend.age} years old`;

                  // 削除ボタンの追加
                  const deleteButton = document.createElement("button");
                  deleteButton.textContent = "Delete";
                  deleteButton.onclick = () => deleteData(friend.id);
                  listItem.appendChild(deleteButton);

                  // 更新ボタンの追加
                  const updateButton = document.createElement("button");
                  updateButton.textContent = "Update";
                  updateButton.onclick = () => updateData(friend.id);
                  listItem.appendChild(updateButton);

                  dataList.appendChild(listItem);
              });
          }).catch(error => {
              console.error("Error displaying data:", error);
          });
      }

      // データの削除
      function deleteData(id) {
          db.friends.delete(id).then(() => {
              console.log("Data deleted from IndexedDB");
              displayData(); // 再表示
          }).catch(error => {
              console.error("Error deleting data:", error);
          });
      }

      // データの更新
      function updateData(id) {
          const newName = prompt("Enter new name:");
          const newAge = parseInt(prompt("Enter new age:"));

          db.friends.update(id, {name: newName, age: newAge}).then(() => {
              console.log("Data updated in IndexedDB");
              displayData(); // 再表示
          }).catch(error => {
              console.error("Error updating data:", error);
          });
      }

      // ページの再読み込み
      function reloadPage() {
          location.reload();
      }

      // ページロード時にデータ表示
      window.onload = displayData;
  </script>
</body>
</html>

LocalForage

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LocalForage Example</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js"></script>
</head>
<body>
  <button onclick="addData()">Add Data to LocalForage</button>
  <button onclick="reloadPage()">Reload Page</button>

  <ul id="dataList"></ul>

  <script>
      // LocalForageインスタンスの作成
      const db = localforage.createInstance({
          name: "myDatabase"
      });
      
      // データの登録
      function addData() {
          const friend = { name: "Alice", age: 30 };
          db.getItem('friends').then(existingFriends => {
              const friends = existingFriends || [];
              friends.push(friend);
              return db.setItem('friends', friends);
          }).then(() => {
              console.log("Data added to LocalForage");
              displayData();
          }).catch(error => {
              console.error("Error adding data:", error);
          });
      }

      // データの参照と表示
      function displayData() {
          const dataList = document.getElementById("dataList");
          dataList.innerHTML = ""; // Clear previous data

          db.getItem('friends').then(friends => {
              friends.forEach((friend, id) => {
                  const listItem = document.createElement("li");
                  listItem.textContent = `${friend.name}, ${friend.age} years old`;

                  // 削除ボタンの追加
                  const deleteButton = document.createElement("button");
                  deleteButton.textContent = "Delete";
                  deleteButton.onclick = () => deleteData(id);
                  listItem.appendChild(deleteButton);

                  // 更新ボタンの追加
                  const updateButton = document.createElement("button");
                  updateButton.textContent = "Update";
                  updateButton.onclick = () => updateData(id);
                  listItem.appendChild(updateButton);

                  dataList.appendChild(listItem);
              });
          }).catch(error => {
              console.error("Error displaying data:", error);
          });
      }

      // データの削除
      function deleteData(id) {
          db.getItem('friends').then(friends => {
              friends.splice(id, 1);
              return db.setItem('friends', friends);
          }).then(() => {
              console.log("Data deleted from LocalForage");
              displayData();
          }).catch(error => {
              console.error("Error deleting data:", error);
          });
      }

      // データの更新
      function updateData(id) {
          const newName = prompt("Enter new name:");
          const newAge = parseInt(prompt("Enter new age:"));

          db.getItem('friends').then(friends => {
              friends[id] = { name: newName, age: newAge };
              return db.setItem('friends', friends);
          }).then(() => {
              console.log("Data updated in LocalForage");
              displayData();
          }).catch(error => {
              console.error("Error updating data:", error);
          });
      }

      // ページの再読み込み
      function reloadPage() {
          location.reload();
      }

      // ページロード時にデータ表示
      window.onload = displayData;
  </script>
</body>
</html>

Pouchdb

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PouchDB Example</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pouchdb/7.2.2/pouchdb.min.js"></script>
</head>
<body>
    <button onclick="addData()">Add Data to PouchDB</button>
    <button onclick="reloadPage()">Reload Page</button>

    <ul id="dataList"></ul>

    <script>
        // PouchDBインスタンスの作成
        const db = new PouchDB('myDatabase');

        // データの登録
        function addData() {
            const doc = {
                _id: new Date().toISOString(),
                name: 'Alice',
                age: 30
            };
            
            db.put(doc).then(() => {
                console.log("Data added to PouchDB");
                displayData();
            }).catch(error => {
                console.error("Error adding data:", error);
            });
        }

        // データの参照と表示
        function displayData() {
            const dataList = document.getElementById("dataList");
            dataList.innerHTML = ""; // Clear previous data

            db.allDocs({include_docs: true}).then(docs => {
                docs.rows.forEach(row => {
                    const doc = row.doc;
                    if (doc.name && doc.age) {
                        const listItem = document.createElement("li");
                        listItem.textContent = `${doc.name}, ${doc.age} years old`;

                        // 削除ボタンの追加
                        const deleteButton = document.createElement("button");
                        deleteButton.textContent = "Delete";
                        deleteButton.onclick = () => deleteData(doc._id);
                        listItem.appendChild(deleteButton);

                        // 更新ボタンの追加
                        const updateButton = document.createElement("button");
                        updateButton.textContent = "Update";
                        updateButton.onclick = () => updateData(doc._id, doc.name, doc.age);
                        listItem.appendChild(updateButton);

                        dataList.appendChild(listItem);
                    }
                });
            }).catch(error => {
                console.error("Error displaying data:", error);
            });
        }

        // データの削除
        function deleteData(id) {
            db.get(id).then(doc => {
                return db.remove(doc);
            }).then(() => {
                console.log("Data deleted from PouchDB");
                displayData(); // 再表示
            }).catch(error => {
                console.error("Error deleting data:", error);
            });
        }

        // データの更新
        function updateData(id, name, age) {
            const newName = prompt("Enter new name:", name);
            const newAge = parseInt(prompt("Enter new age:", age));

            db.get(id).then(doc => {
                doc.name = newName;
                doc.age = newAge;
                return db.put(doc);
            }).then(() => {
                console.log("Data updated in PouchDB");
                displayData(); // 再表示
            }).catch(error => {
                console.error("Error updating data:", error);
            });
        }

        // ページの再読み込み
        function reloadPage() {
            location.reload();
        }

        // ページロード時にデータ表示
        window.onload = displayData;
    </script>
</body>
</html>

Lovefield

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Lovefield Example</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lovefield/2.1.12/lovefield.min.js"></script>
</head>
<body>
  <button onclick="addData()">Add Data to IndexedDB</button>
  <button onclick="reloadPage()">Reload Page</button>

  <ul id="dataList"></ul>

  <script>
      var schemaBuilder = lf.schema.create('todo', 1);

      schemaBuilder.createTable('Item')
          .addColumn('id', lf.Type.INTEGER)
          .addColumn('description', lf.Type.STRING)
          .addColumn('deadline', lf.Type.DATE_TIME)
          .addColumn('done', lf.Type.BOOLEAN)
          .addPrimaryKey(['id'], true)
          .addIndex('idxDeadline', ['deadline'], false, lf.Order.DESC);

      var todoDb;
      var item;

      schemaBuilder.connect().then(function(db) {
          todoDb = db;
          item = db.getSchema().table('Item');
          displayData();
      });

      // データの登録
      function addData() {
          var row = item.createRow({
              'description': 'Get a cup of coffee',
              'deadline': new Date(),
              'done': false
          });

          todoDb.insertOrReplace().into(item).values([row]).exec().then(() => {
              console.log("Data added to IndexedDB");
              displayData();
          }).catch(error => {
              console.error("Error adding data:", error);
          });
      }

      // データの参照と表示
      function displayData() {
          const dataList = document.getElementById("dataList");
          dataList.innerHTML = ""; // Clear previous data

          todoDb.select().from(item).exec().then(rows => {
              rows.forEach(row => {
                  const listItem = document.createElement("li");
                  listItem.textContent = `${row.description}, deadline: ${row.deadline}`;

                  // 削除ボタンの追加
                  const deleteButton = document.createElement("button");
                  deleteButton.textContent = "Delete";
                  deleteButton.onclick = () => deleteData(row.id);
                  listItem.appendChild(deleteButton);

                  // 更新ボタンの追加
                  const updateButton = document.createElement("button");
                  updateButton.textContent = "Update";
                  updateButton.onclick = () => updateData(row.id);
                  listItem.appendChild(updateButton);

                  dataList.appendChild(listItem);
              });
          }).catch(error => {
              console.error("Error displaying data:", error);
          });
      }

      // データの削除
      function deleteData(id) {
          todoDb.delete().from(item).where(item.id.eq(id)).exec().then(() => {
              console.log("Data deleted from IndexedDB");
              displayData(); // 再表示
          }).catch(error => {
              console.error("Error deleting data:", error);
          });
      }

      // データの更新
      function updateData(id) {
          const newDescription = prompt("Enter new description:");
          const newDeadline = new Date(prompt("Enter new deadline (YYYY-MM-DD):"));

          todoDb.update(item)
            .set(item.description, newDescription)
            .set(item.deadline, newDeadline)
            .where(item.id.eq(id))
            .exec().then(() => {
              console.log("Data updated in IndexedDB");
              displayData(); // 再表示
          }).catch(error => {
              console.error("Error updating data:", error);
          });
      }

      // ページの再読み込み
      function reloadPage() {
          location.reload();
      }

      // ページロード時にデータ表示
      window.onload = displayData;
  </script>
</body>
</html>

フロントjsで使えないもの(使わないもの)

LokiJS

IndexedDBのラッパー?
参考記事が少なく、検索しても他のLoki jsがヒットしてしまうので除外しました。

AlaSQL

localStorage, Excelにアクセスできるみたいです。

localStorageは使うつもりがないのと、
Excelへのアクセスに関してはCORSエラーになります。
サーバーを立てれば解決しますが、フロントjsのみの場合は
サーバーを立てれないので除外しました。

Local Storageを使用しないこと
メモ帳作る程度なら気にしなくてよさそうだが
パスワードをメモするつもりならやめた方がいい
https://techracho.bpsinc.jp/hachi8833/2024_04_05/80851

CORSエラー
ローカルファイルを読み込もうとするとエラーになる
サーバを立てれば解決可能
https://ugo.tokyo/same-origin-policy/

lowdb

json Serverのリソース更新で使用されるみたいです。
Node.jsが必要そうなので除外しました。

json Server
モックサーバを立てれる
https://majisemi.com/topics/oss/4925/#:~:text=JSON%20Server(%E3%82%B8%E3%82%A7%E3%82%A4%E3%82%BD%E3%83%B3%20%E3%82%B5%E3%83%BC%E3%83%90)%E3%81%A8,%E3%81%AA%E3%81%A9%E3%81%AB%E6%B4%BB%E7%94%A8%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82

NeDB

組み込み型DB
Electronなどで使用されるみたいです。

たぶんNode.jsが必要です。(ちゃんと調べてない)
※Node.jsが不要でも組み込みDBならsqliteみたいにファイルに
 アクセスすることになるのでCORSエラーになると思います。

フロントjsのみで解決したいので除外しました。

Electron
フロント技術でパッケージ作成する
osに依存しない
https://www.electronjs.org/ja/docs/latest

RxDB

オフラインで貯蓄したデータをオンライン時に別DBと同期できるそうです。
Node.jsが必要そうなので除外しました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0