第7章 WEBアプリ開発(フロントエンドWEBアプリ)

PCやスマホの画面にデータを表示したり、データを追加したりできるアプリをHTMLとCSSで記述します
7-1. WEBページの制作(HTML)
7-2. フロントWEBアプリのJavaScriptコード
7-3. バックエンドAPIアプリ(再掲)

7-1. WEBページの制作(HTML)

ファイル名は「index.html」とします

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ToDoアプリ</title>
    <script type="text/javascript" src="crud.js"></script>
</head>
<body>
    <h1>ToDoアプリ</h1>
    <div id="server"></div>
    <!-- 入力画面 -->
    <p>やりたいこと:<input type="text" id="inputPlan" placeholder="計画を追加する"></p>
    <p>実績    :<input type="text" id="inputResult" placeholder="補足があればメモする"></p>
    <p>更新/削除するID :<input type="text" id="inputMyID" placeholder="更新または削除するID"></p>

    <!-- レスポンス画面 -->
    <div id="output"></div>

    <!-- メニューボタン -->
    <button onclick="itemAdd()">データを追加</button>
    <button onclick="itemRead()">データを取得</button>
    <button onclick="itemDelete()">データを削除</button>
    <button onclick="itemUpdate()">データを更新</button>
</body>
</html>

7-2. フロントWEBアプリのJavaScriptコード

ファイル名は「crud.js」とします

let server = "https://サーバーのURL/api3003";

// -------------------------------
// HTML
// -------------------------------
window.onload = function start() {
    document.getElementById('server').innerHTML = '<p>' + server + '</p>';
}

// ----------------------------------------------
// データ読み取り
// ----------------------------------------------
async function itemRead() {
    // API呼び出し実行
    let url;
    url = server + '/get';
    const response = await fetch(url, {mode:'cors'});
    const jsonObj = await response.json();

    // HTML作成
    let strFlag = "";
    let html = '<table class="table table-bordered"><tr><th>ID</th><th>項目1</th><th>PLAN</th><th>MEMO</th><th>項目4</th></tr>';
    jsonObj.forEach(item => {
        if(item.flag == "0") {
            strFlag = "<i class='bi bi-check-square-fill'></i>";
        } else {
            strFlag = "";
        }
        html += `<tr><td>${item.id}</td><td style="text-align:center;">${strFlag}</td><td>${item.plan}</td><td>${item.result}</td><td>`
        + `<button onclick="window.location.href='edit.html?`
        + `id=${item.id}&flag=${item.flag}&plan=${item.plan}&memo=${item.result}`
        + `';" class="btn btn-secondary">...</button>`
        + `${item.id}</td></tr>`;
    });
    html += '</table>';
    document.getElementById('output').innerHTML = html;
}


// ----------------------------------------------
// データ作成
// ----------------------------------------------
async function itemAdd() {
    // HTMLから要素を取得
    var inputPlan = document.getElementById('inputPlan').value;
    var inputResult = document.getElementById('inputResult').value;
    console.log("Result:"+inputResult)

    // API呼び出しを実行
    const url = `${server}/create`;
    const response = await fetch(url, {
        method: "POST",
        mode: "cors",
        headers: {
            "Content-Type": "application/json"        
        },
        body: JSON.stringify({
            plan: inputPlan,
            result: inputResult
        })
    });

    // HTMLに結果を表示
    let html = "データを書き込みました";
    document.getElementById('output').innerHTML = html;
}

// ----------------------------------------------
// データ更新
// ----------------------------------------------
async function itemUpdate() {
    // HTMLから要素を取得
    var myID = document.getElementById('inputMyID').value;
    var plan = document.getElementById('inputPlan').value.slice(0, 249);
    var result = document.getElementById('inputResult').value.slice(0, 249);
    if (!myID) {
        document.getElementById('output').innerHTML = '<p>更新するIDが不足しています<i class="fa-solid fa-pen"></i></p>';
        return;
    }

    // API呼び出しを実行
    document.getElementById('output').innerHTML = '<p>データを変更しています<i class="fa fa-cog fa-spin fa-3x fa-fw"></i></p>';
    const url = `${server}/update/${myID}`; // IDをURLパスに含める
    const response = await fetch(url, {
        method: "PUT",
        mode: "cors",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            plan: plan,
            result: result
        })
    });

    // レコード更新が成功したか確認
    if (!response.ok) {
        throw new Error(`(updateName) APIサーバーに届きません。ステータス: ${response.status} ${response.statusText}`);
    }

    // レコード更新が成功した場合のメッセージ
    let html = `レコード番号[${myID}]を更新しました`;
    document.getElementById('output').innerHTML = html;
}

// ----------------------------------------------
// データ削除
// ----------------------------------------------
async function itemDelete() {
    var targetID = document.getElementById('inputMyID').value;
    if (targetID === '') {
        document.getElementById('output').innerHTML = '<p>削除するIDが不足しています<i class="fa-solid fa-pen"></i></p>';
        return;
    }
    document.getElementById('output').innerHTML = '<p>削除しています</p><i class="fa fa-cog fa-spin fa-3x fa-fw"></i>';
    const url = `${server}/delete/${targetID}`; //  IDをURLのパスに含める
    const response = await fetch(url, {
        method: "DELETE",
        mode: "cors"
    });

    let html = `ID[${targetID}]を削除しました`;
    document.getElementById('output').innerHTML = html;
}

7-3. バックエンドAPIアプリ(再掲)

念のため、動作確認済みのNode.js で動くBFFを再掲します。ファイル名は「app-api.js」で、PM2でプロセスを起動しています。

// ----------------------------
// 初期設定:モジュール読み込み。変数定義。
// ----------------------------
const express = require("express");                 // Expressモジュール読み込み
const app = express();                              // アプリサーバーとしてインスタンス化
const mysql = require('mysql');                     // MySQLモジュール読み込み
const cors = require('cors');                       // CORS(Cross-Origin Resource Sharing)モジュールを読み込み
const port = 3003;                                  // TCPポート番号を指定
require('dotenv').config();                         //環境変数[~/app/.env]を process.env に代入

// ----------------------------
// `.bashrc` で設定した環境変数からユーザー情報を取得
// ----------------------------

// ----------------------------
// listen()メソッドを実行して3000番台ポートで待ち受け。
// ----------------------------
var server = app.listen(port, function(){
    console.log("Node.js待ち受けポート:" + server.address().port);
});

// ----------------------------
// WEBブラウザは、異なるオリジン(ドメイン)へのアクセスは基本的にブロックする。
// CORS()関数は、リモートサーバーからのPOST, GET, PUT, DELETEメソッドのアクセスを許可する。詳細な制御も可能。
// ----------------------------
//app.use(cors({
//    origin: 'https://example.com',
//    methods: ['GET', 'POST', 'PUT', 'DELETE'],
//    allowedHeaders: ['Content-Type', 'Authorization']
//  }));
//app.use(cors());

// ----------------------------
// フォームデータを解析
// ----------------------------
app.use(express.urlencoded({ extended: true })); // フォーム形式(name=value)
app.use(express.json());                         // JSON形式({ "key": "value" })


// ------------------------------------
// データベースに接続する関数
// ------------------------------------
function connectDatabase() {
  const db = mysql.createConnection({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASS,
    database: process.env.DB_NAME
  });

  db.connect((err) => {
    if (err) {
      console.error('データベース接続失敗: ' + err.message);
      throw err;
    }
  });

  return db;    
}

// ------------------------------------
// データベースを閉じる関数
// ------------------------------------
function closeDatabase(db) {
    db.end((err) => {
        if (err) {
          console.error('データベース切断失敗: ' + err.message);
        }
      });    
}

// ------------------------------------
// データ読み込み
// ------------------------------------
app.get('/get', (req, res) => {
  // 1.データベースに接続
  const db = connectDatabase(); 
  // 2.データを読み出すクエリ
  let sql = 'SELECT * FROM tbl_kano;';
  db.query(sql, (err, results) => {
      if (err) throw err;
      res.json(results);
      // 3.データベースを閉じる
      closeDatabase(db); 
  });
});

// ------------------------------------
// データ書き込み
// ------------------------------------
app.post('/create', (req, res) => {
    // 1.データベースに接続
    const db = connectDatabase();
    //plan memo
    var inputPlan = req.body.plan;
    var inputResult = req.body.result;

    // 2.データを書き込むクエリ
    sql = "INSERT INTO tbl_kano (plan, result, flag) VALUES ('" + inputPlan + "', '" + inputResult + "', 0)";

    db.query(sql, (err, results) => {
      if (err) {
        console.log('DB書き込みに失敗 X');
        throw err;
      }
      res.json(results);
      console.log('DB書き込み成功 O');
    });

    // 3.データベースを閉じる
    closeDatabase(db);
});

// ------------------------------------
// データ削除
// ------------------------------------
app.delete('/delete/:id', (req, res) => {
  // 1.データベースに接続
  const db = connectDatabase();
  // 2.URLパラメータから `id` を取得(修正: `req.params.id` を使用)
  let myID = req.params.id;
  // IDが空ならエラーを返す
  if (!myID) {
    return res.status(400).json({ message: "削除するIDが指定されていません。" });
  }
  // SQLクエリを安全に実行
  let sql = "DELETE FROM tbl_kano WHERE id = ?";
  db.query(sql, [myID], (err, results) => {
    if (err) {
      console.error("DBのレコード削除に失敗:", err);
      return res.status(500).json({ message: "データベースエラー" });
    }
    res.json({ message: `ID[${myID}] を削除しました`, data: results });

  });
  // 3.データベースを閉じる
  closeDatabase(db);
});

// ------------------------------------
// データ更新
// ------------------------------------
app.put('/update/:id', (req, res) => {
  // 1.データベースに接続
  const db = connectDatabase();

  // 2.URLパラメータから `id` を取得
  let myID = req.params.id;
  let myPlan = req.body.plan;
  let myResult = req.body.result;

  // リクエスト検査
  if (!myID) {
    console.log("更新するID[%d]が不足しています。", myID);
    return res.status(400).json({ message: "更新するIDまたはデータが不足しています。" });
  }

  // SQLクエリを実行
  let sql = "UPDATE tbl_kano SET plan = ?, result = ? WHERE id = ?";
  db.query(sql, [myPlan, myResult, myID], (err, results) => {
    if (err) {
      console.error("レコードの更新に失敗:", err);
      return res.status(500).json({ message: "データベースエラー" });
    }
    res.json({ message: `ID[${myID}] のデータを更新しました`, data: results });
  });
  console.log("[9]データベース更新を終了しDB閉じます");

  // 3.データベースを閉じる
  closeDatabase(db);
});

// ------------------------------------
// アプリサーバーが応答することを検査
// ------------------------------------
app.get('/', (req, res) => {
    res.send('いえーい');
    console.log('いえーい');
});

コメントを残す