はじめに
趣味でプログラミングをする際にフロント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が必要そうなので除外しました。
NeDB
組み込み型DB
Electronなどで使用されるみたいです。
たぶんNode.jsが必要です。(ちゃんと調べてない)
※Node.jsが不要でも組み込みDBならsqliteみたいにファイルに
アクセスすることになるのでCORSエラーになると思います。
フロントjsのみで解決したいので除外しました。
Electron
フロント技術でパッケージ作成する
osに依存しない
https://www.electronjs.org/ja/docs/latest
RxDB
オフラインで貯蓄したデータをオンライン時に別DBと同期できるそうです。
Node.jsが必要そうなので除外しました。