2021年6月より『Monaca Education』に搭載された『簡易データベース』機能を使用したサンプルアプリの『お小遣い帳』を紹介します。

サンプルアプリのインポート

お小遣いアプリ(インポート)

※簡易データベースの仕様については、以下のページをご参照ください。
Monaca Education簡易データベース・チュートリアル&仕様

エンドポイントの確認

以下のURLにブラウザで直接アクセスすれば、JSONデータを確認できます。

まぁ、見たところで、日本語の部分がUnicodeでエンコードされており、イメージが付きにくいかもしれませんが、『データが何件が入っている』ということがイメージできればOKです。

ところで、この記事やサンプルアプリではAPIキーを掲載していますが

「APIキーが丸見えで良いの?」

と思われるかもしれません。

はい、普通はダメです。

しかし、このAPIキーは読み取り専用キーなので問題ありません。

値を参照することはできても、更新や削除はできません。
例えば、以下のリクエストは正しい削除のリクエストですが、失敗します。

読み取り専用キーは、削除・追加・更新などの目的では全く使えない仕様となっています。
そのため、情報が閲覧されてしまうと言うこと以外は、漏洩しても全く問題ありません。

とはいえ、リクエスト毎に課金が発生するようなAPIの参照キーが漏洩したら問題ですので、ケースバイケースです。

『Monaca Education 簡易データベース』の参照キーは、データベースの中に、見られては困るデータを登録していない限りは、漏れても大丈夫だけど、ケースバイケース。

ということです。

例えば、一定の回数、連続で参照したら、イタズラ防止や無限ループ対策のためにリクエストを無視する、かもしれません。また、それによって、通常の問い合わせも一定期間、行えなくなる可能性もあります。そのため、漏れないに越したことはありません。

なお、漏れてしまったときは、キーをリセットして変更することは可能です。

お小遣い帳アプリの概要

データベース学習に必要な『登録・参照・更新・削除』の4機能を1画面に収めているため、ちょっと縦に長いです。

4機能のことをプログラミングの世界では『CRUD』とも呼びます。

  • Create
  • Read
  • Update
  • Delete

アプリでCRUDを実現するためには、ファイルに値を読み書きするか、データベースに値を読み書きする必要があります。
また、HTML5では『ローカルストレージ』という、アプリやWepサイト単位(正確にはオリジン単位)で端末の中に値を読み書きする仕組みもあります。

今回のお小遣い帳アプリでは、『Monaca Educationの簡易データベース』に値を読み書きすることでCRUDを実現しています。

なお、データベースの世界では『CRUD』を実現するための命令群を『データ操作言語(DML)』と呼びます。

  • INSERT
  • SELECT
  • UPDATE
  • DELETE

の4つの命令が、リレーショナルデータベースで使われるSQL言語には備わっています。

Monaca Educationの簡易データベースとは

SQL言語風のWebAPIを備えたNoSQL型のデータベースです。
SQL言語風なので、INSERT・SELECT・UPDATE・DELETEの4つの命令が使えます。
しかしNoSQL型なので、情報を複数のテーブルに分割する正規化やテーブル同士を結合するリレーションの概念はありません。
どちらかというと、HTML5のローカルストレージに近いものです。

お小遣いの記録・表示部分の解説

お小遣いの記録の部分では、データベースの取得結果を表示しています。
ほぼ無加工で、データベースの中の値をtableタグで表示しているだけですが、一部、収入なのか支出なのかの表示では条件分岐を使用しています。

ソースコード(HTML側)

HTML側ではtableタグで一覧表のひな形を用意します。見出しの行は直接HTMLで記述しますが、実際のデータ行はプログラムで1行ずつ、追記していきます。また、tableにDOMでアクセスできるよう、ID名として『moneyRecord』を設定しています


    <table border="1" id="moneyRecord">
        <caption>お小遣いの記録</caption>
        <tr>
            <th>ID</th>
            <th>名目</th>
            <th>金額</th>
            <th>フロー</th>
        </tr>
    </table>

ソースコード①DB接続と取得(JavaScript側)

まず、main.jsの1行目でAPIキーを変数に格納しています。
次に、2行目では参照用の関数select()を読んでいます。

関数select()では、apiキーを活用してデータベースへのWebAPIリクエスト文字列を組み立てた上で、fetch()命令を読んでいます。

このリクエストに成功したら、データベースの値がJSONデータとして取得できるので、それを変数dbに展開します。

データベース固有の記述はここまでです、以降は、ただの配列と繰り返し処理に過ぎません。


let apikey = "8c6d68a4-7bb8-4d62-84ff-348b47a1fbda";
select();

function select () {
    let url = "https://db.monaca.education/v1/select?apikey=" + apikey;
    console.log(url);
    fetch(url)
    .then(function(response) {
        return response.json();
    })
    .then(function(db) {
            console.log(db);

ソースコード②(JavaScript側)

ただの配列と繰り返し処理、それと、DOMでtableタグにデータを追記する処理を解説します。

まずはお小遣いの記録を表示するtable要素をDOMとして取得します。
ID名は『moneyRecord』です。

次に、データベースの値が格納されている変数dbをforループに渡して、データの行数文だけループ処理を回します。

収入か支出を判断する部分だけは、if文で情研分けしていますが、基本的には、DOMの組み立てしか行っていません。


let moneyRecord = document.getElementById("moneyRecord");

for (let i = 0; i < db.records.length; i++) {
    // DOM準備
    let tr = document.createElement("tr");
    let id = document.createElement("th");
    let title = document.createElement("td");
    let price = document.createElement("td");
    let flow = document.createElement("td");
    // DOMに値を格納
    id.innerHTML = db.records[i].serial;
    title.innerHTML = db.records[i].text1;
    price.innerHTML = db.records[i].int1;
    if(db.records[i].int2 == 1) {
        flow.innerHTML = "収入";
    } else if (db.records[i].int2 == -1) {
        flow.innerHTML = "支出";
    } else {
        flow.innerHTML = "???";
    }
    // 構築したDOM要素を表に追加
    tr.appendChild(id);
    tr.appendChild(title);
    tr.appendChild(price);
    tr.appendChild(flow);
    moneyRecord.appendChild(tr);
}

appendChildという命令は、DOM要素の中にDOM要素を追加する命令です。
table要素の中に行を表すtr要素を追記するのに使っています。
また、tr要素に、th要素やtd要素を追加するのにも使っています。

要するに、HTML側にひな形として用意したお小遣い帳のtable要素に対して、DOM要素を入れ子上に追加することで完成させています。

データベースの列名とお小遣い帳の列名の関係

簡易データベースは表計算ソフトのように列名が固定です。
text1やint1など、列名は既に決まっているため、参照するときにも、その名前でしか呼び出せません。


title.innerHTML = db.records[i].text1;

これは、先ほどのJavaScriptのコードの一部です。
DB側のtext1をお小遣い帳のタイトルとして利用しています。

金額の部分では、int1が使われています。


price.innerHTML = db.records[i].int1;

DB側の列を、プログラムの中で何に使うかは、プログラムを書く側の自由、ということです。
なお、一般的なRDB(リレーショナルデータベース)の場合には、列名をDB設計者側が先に決める形になります。

また、収入や支出を表す部分では、int2が使われています


if(db.records[i].int2 == 1) {
    flow.innerHTML = "収入";
} else if (db.records[i].int2 == -1) {
    flow.innerHTML = "支出";
} else {
    flow.innerHTML = "???";
}

値が1だったら収入、-1だったら支出、それ以外だったら???としています。
これも、プログラム側で自由に決めています。
また、これはフラグの一種でもあります。

なお、小さなスマホアプリでしたら、これで全く問題ないのですが、システムという規模のソフトウェアを本当に開発するときは、『1が収入で-1が支出』のような、全体の設計に関わるような情報は、然るべき箇所にまとめて記述するような工夫が必要になります。

お小遣い長の記録・収入と支出の合計結果

記録を表示するだけだと芸がないので、いま、いくら持っているのかを集計で出すプログラムもアプリに含めておきました。

HTML側

収入の合計はID『incomeTotal』、支出の合計はID『outgoTotal』、所持金はID『money』としました。


<form id="deleteForm" autocomplete="off">
    ID:<input type="number" name="serial">
    <button type="button" onclick="deleteDB()">削除</button> 
</form>

記録の時と異なり、DOM操作で表の行を追記する必要はないので、JavaScript側は先ほどより、少し短くなる、はず。たぶん。

JavaScript側

変数incomeTotalとoutgoTotalを0円スタートで宣言し、データベースの取得結果が入った変数dbでforループを回します。処理の中では、if文による分岐で、記録のデータが収入だったらincomeTotalに加算代入、支出だったoutogoTotalに加算代入していきます。フラグが-1でも1でもないものは無視。

最後に、DOMを使って収入の合計値と支出の合計値を表に反映。さらに、収入と支出の差額を所持金として表に反映しています。


// 集計
let incomeTotal = 0;
let outgoTotal  = 0;
for (let i = 0; i < db.records.length; i++) {
    if(db.records[i].int2 == 1) {
        incomeTotal += db.records[i].int1;
    } else if (db.records[i].int2 == -1) {
        outgoTotal += db.records[i].int1;
    } else {
        // 不明な記録は無視
    }
}
document.getElementById("incomeTotal").innerHTML = incomeTotal;
document.getElementById("outgoTotal").innerHTML  = outgoTotal;
document.getElementById("money").innerHTML       = incomeTotal - outgoTotal;

お小遣いの記録・記録部分の解説

このアプリはデータベース連携アプリなので、当然、記録を付けることもできます。
また、データはアプリの中ではなく、クラウドのデータベースに記録されているので、アプリを再インストールしても、データは消えません。
(データベース側が消えたら、もちろん、データだけ、消えますが…)

入力フォームに値を入れて『登録』ボタンを押せば、記録が行われます。
なお、キーをマスターキーに変えないと、記録は行われません。

ソースコード(HTML側)


<form id="insertForm" autocomplete="off">
    名目:<input type="text" name="title"><br>
    金額:<input type="number" name="price" ><br>
    <input type="radio" name="flow" value="1" >収入
    <input type="radio" name="flow" value="-1" checked>支出<br>
    <button type="button" onclick="insert()">登録</button> 
</form>

登録ボタンをクリックしたら『insert()』関数が実行されるように仕込んであります。

ソースコード(JavaScript側)

フォームの値をDOMで取得し、値を文字列連結した上でWebAPIのリクエストを組み立ててfetch()しています。
意外なことに、本当にそれしかやっていません。
分岐も繰り返しもありません。

なお、text1がtitleで、int1がpriceでint2が収入と支出のフラグで使われていることは、参照のケースと同じです。


function insert (){
    // フォームの値を取得
    let form = document.getElementById("insertForm");
    let title = form.title.value;
    let price = form.price.value;
    let flow = form.flow.value;

    // DBに登録
    let url = "https://db.monaca.education/v1/insert?apikey=" + apikey;
    url += "&text1=" + title + "&int1=" + price + "&int2=" + flow;
    fetch(url)
        .then(function(response) {
            return response.json();
        })
        .then(function(result) {
            console.log(result);
            location.reload();
    });
}

お小遣いの記録・変更部分の解説

新規で記録するときと変更の違いは、変更対象を絞り込むための鍵となる情報も必要な点です。
特定の行だけをピンポイントで変更したい場合は、IDを使うのが簡単です。

データベースでは、だいたい、『ID』が存在するため、今回はIDを指定して情報を変更することとします。

簡易データベースでは、自動的に連番のID(serial)が振られます。

HTML側


<h2>修正する</h2>
<form id="updateForm" autocomplete="off">
    ID:<input type="number" name="serial"><br>
    名目:<input type="text" name="title"><br>
    金額:<input type="number" id="price"><br>
    <input type="radio" name="flow" value="1" >収入
    <input type="radio" name="flow" value="-1" checked>支出<br>
    <button type="button" onclick="update()">更新</button> 
</form>

ID情報を入力するためのフォーム要素が増えています。

ソースコード(JavaScript側)

登録の時とほとんど同じですが、fetch()に渡す文字列に一部違いがあります。
『insert』ではなく『update』というURLにしているいることに加え、更新対象を指定するために『&serialIn』というパラメーターを含めています。

なお、&serialInを含めない場合には、すべてのレコードがupdateされます。これは簡易データベースの仕様というよりは、一般的なRDB(リレーショナルデータベース)全般の仕様です。また、データベースサーバーというのはアプリの裏方として動くものなので、確認なども出ずに(出しようがない)、一撃でデータを更新したり削除します。


function update() {
    // フォームの値を取得
    let form = document.getElementById("updateForm");
    let serial = form.serial.value;
    let title = form.title.value;
    let price = form.price.value;
    let flow = form.flow.value;

    // DB問い合わせ
    let url = "https://db.monaca.education/v1/update?apikey=" + apikey;
    url += "&serialIn=" + serial;
    url += "&text1=" + title + "&int1=" + price + "&int2=" + flow;
    console.log(url);
    fetch(url)
        .then(function(response) {
            return response.json();
        })
        .then(function(result) {
            location.reload();
    });
}

お小遣いの記録・削除部分の解説

削除は一番簡単です、IDを指定して消すだけです。

HTML側

フォーム要素のID名は『deleteForm』としました。


<form id="deleteForm" autocomplete="off">
    ID:<input type="number" name="serial">
    <button type="button" onclick="deleteDB()">削除</button> 
</form>

JavaScript側

deleteFormのDOM要素を取得して削除したい行のIDを変数serialに格納します。
次に、WebAPI問い合わせ用の文字列を組み立ててfetch()に渡します。

なお、updateの時と同様にserialInで対象の絞りこみを行っています。
やはり、&serialInを含めない場合には、すべてのレコードがdeleteされます。
くどいようですが、データベースサーバーというのはアプリの裏方として動くものなので、確認なども出ずに、一撃でデータを更新したり削除します。慈悲はない。


function deleteDB() {
    // フォームの値を取得
    let form = document.getElementById("deleteForm");
    let serial = form.serial.value;

    // DB問い合わせ
    let url = "https://db.monaca.education/v1/delete?apikey=" + apikey;
    url += "&serialIn=" + serial;
    console.log(url);
    fetch(url)
        .then(function(response) {
            return response.json();
        })
        .then(function(result) {
            console.log(result);
            location.reload();
    });
}

まぁ、データベースはバックアップが取れるので、必要に応じて取ってください。
なお、Monaca Education簡易データベースはβ版なので、バックアップ機能はありますが、正常に動く保証はありません。