【Node.js】Express.js / MySQL / ejs / を使ったDBのCRUDアプリ


目的

現在、MySQLに保管した店舗データを編集できるアプリケーションを作成しています。Node.jsのexpress.jsを使用し、viewにはejsを利用しています。この記事では、初学者の私がつまずいた、パラメータの受け渡しについて記述します。

ずばり「企業一覧画面から、企業に属した店舗の一覧を表示させる」処理についてです。

一連の処理は、こんな感じです

企業+店舗一覧を取得(MySQL)
→ 企業一覧画面で企業名をクリック(ejs)
→ 店舗一覧画面(ejs)
→ 店舗名のリンクをクリック (ejs)
→ ★ express.js(app.js)でデータを加工(MySQL)
→ ★ 加工したデータをejsに渡す(express.js)
→ ★ ejsで加工したデータを表示させる

対象読者

・Express初心者
・アプリ作成初心者
・MySQL初心者

環境と周辺構造

・local (mac Big Sur)
・AWS MySQL

基本となるデータベースとの接続やCRUD処理の基礎的な書き方等については、主にリンク先の記事を参考に作成しました。

参照:Express.js(node.js)からMySQLへの接続とCRUD操作

テーブル構造

テーブルの相関関係は以下の通りです。
account_masterが企業、shop_masterが企業が運営する店舗と仮定し作成しています。

テーブルの関係性を簡単に表すと、
account_master.account_id = shop_master.shop_account_idです。
企業側のaccount_idは店舗側のshop_account_idと同じことを意味しています。

画面イメージ

少し雑ですが、画面のイメージは以下の通りです。
・企業 一覧画面(account_master_index.ejs)

・店舗一覧画面(shop_index)

企業名リンクをクリックすると、店舗一覧画面に遷移します。

ディレクトリ構造

※現在、未完成のため一部のみ記載

account_app/
├── node_modules
├── views
│   ├── account_master_index.ejs
│   ├── edit.ejs
│   └── shop_index.ejs
├── .env
├── .gitignore
├── app.js
├── package.json
└── README.md

app.jsにexpress.jsやMySQLの処理を記述し、viewsの中でテンプレートを作成しています。

前提

参考サイトでは、簡易なテーブルのデータで作成されていたため、企業の一覧や、店舗の一覧は簡単に表示まで出来ました。しかし、企業に基づく店舗など関係性がちょっと複雑な場合、どう表示させればよいかが全く分かりませんでした。

先に企業一覧について、次に店舗一覧について記述します。

企業一覧画面

app.js

企業一覧画面は参考サイトを元に、テーブル名、select文を変えるだけでOKでした。※記述外の設定は参考サイトを参照ください。


//app.jsの企業一覧に関する部分
app.get('/', (req, res) => {
    const sql = "select * from account_master where is_deleted = 0;
    con.query(sql, function (err, result, fields) {
    if (err) throw err;
    res.render('account_master_index',{account_master : result});
    });
});

app.getは 第一引数に指定したURLに対応しており、express.js のアプリケーションのルート( 今回は、localhost:3000 )へのGETメソッドに対応します。con.queryselect * from account_master where is_deleted = 0"を取得。

例外処理を記述した後、res.renderの第一引数にデータを表示させたいテンプレートを設定し、第二引数に .ejs に渡す名前を指定します。この命名により、ejs側ではresultではなくaccount_masterを使って取得したデータを利用できます。

account_master_indx.ejs

一部抜粋した記述です。

//account_master_indx.ejsの中身
    <% account_master.forEach(function (value) { %>
    <tr>
      <td><%= account_id %></td>
  ★   <td><a href="/shop_index/<%= value.account_id %>"><%= value.account_name %></a></td>
      <td><%= value.account_email.split('*') %></td>
      <td><%= value.mail_requested %></td>
      <td><%= value.is_deleted %></td>
      <td><%= value.updated_at %></td>
      <td><%= value.account_form_url %></td>
      <td><a href="/edit/<%= value.ue_account_id %>">up</a></td>
    </tr>
    <% }); %>

参考サイトはforEachを使っていたため、そのまま使っています。<% %>や、<%= %>で処理を書くのか、 HTML として表示させるかを書きます。

★ここで4行目に注目
<a href="/shop_index/<%= value.account_id %>"><%= value.account_name %></a>で企業 id のパラメータをURLに指定しています。後にこれと同じURLのパラメータとして表示させる処理をreq.paramsを使い app.js 内に で記述します。

ここからが今回の記事の本題です

ポイントとしては以下のとおりです。
・個別に呼び出すのではなく、まず店舗の全データを取得する
・配列を新たに作成する
・for文、if文で条件指定する
・加工した「会社一覧に紐づく店舗一覧の配列を作成するデータ」をejs側で表示する

app.jsでデータを加工する

失敗した考え方
店舗一覧画面を出そうした際に、まず企業一覧画面と企業の編集画面を参考にしました。というのも、編集画面は企業のaccount_idを自然に渡せていたからです。そのため、店舗一覧画面も企業一覧と編集画面のようにaccount_idを受け渡せないかと考えました。

そもそも、この↑部分の認識が間違っており、正しくはexpress.js側で処理したデータをejsで表示させる。つまり、ejsからgetメソッドで表示させる場合はejsから値を受け渡すなどは行わないです。

成功した考え方
いきなり企業に紐づく店舗一覧を、expressで記述しそれをejs側で表示させるのではなく、まずapp.js内で先に店舗の全データを出します。そのデータを使って、必要な情報だけを載せた配列を新たに作成します。作成した配列をejs側で呼び出し、表示させます。

店舗一覧画面

店舗一覧画面に関する記述は以下のとおりです。

app.js


//店舗データを取得するための変数を定義
var shopDat;

//店舗の全データを取得
con.query('select * from shop_master where is_deleted = 0;', function (error, results, fields) {
  if (error) throw error;
  shopDat = results; //shopDatに代入
});

~~(中略)~~

//企業ごとの店舗一覧を取得しshop_index.ejsで表示させるためのデータ加工
app.get('/shop_index/:shop_account_id', (req, res) => {
    let shops = []; //新たな配列を作成
    for(let i = 0; i < shopDat.length; i++){
      if (req.params.shop_account_id == shopDat[i].shop_account_id) { //表示するパラメータを指定
        var target_shop = { //配列に入れるオブジェクトデータを定義
            "shop_id": shopDat[i].shop_id,
            "shop_name_jp": shopDat[i].shop_name_jp,
            "shop_name_en": shopDat[i].shop_name_en,
            "shop_account_id": shopDat[i].shop_account_id,
            "is_deleted": shopDat[i].is_deleted,
            "updated_at": shopDat[i].updated_at
        };
            shops.push(target_shop) //空の配列shopsに.pushで追加
        }
    };
    res.render('shop_index',{shop_data :shops}); //shop_dataとしてshop_index.ejsに渡す
});

先に shopDat を宣言し、そこにMySQLから店舗の全データを代入します。店舗一覧画面を表示させる箇所に、 shops という新たな空の配列を作成します。更にi番目の shopDat のデータを保持させる target_shop というオブジェクトを作成します。shops.push(target_shop)を shops という空の配列に、 for 文で作成されたオブジェクトを追加して、加工データを作成します。

この時req.params.shop_account_id == shopDat[i].shop_account_idではURLに渡すパラメータと企業に紐づく店舗の情報を一致させるために記述しています。※shop_account_idは企業のアカウント id と同じです。

req.params はリクエストされたパスからパラメータを取得するに使う文字列です。
[Node.js][Express]リクエストからパラメータを取得する・POSTされたデータを取得する

shop_index.ejs

//店舗一覧画面に関係する部分
    <% for (let i = 0; i < shop_data.length; i++) { %>
      <tr>
        <td><%= shop_data[i].shop_id %></td>
        <td><%= shop_data[i].shop_name_jp %></td>
        <td><%= shop_data[i].shop_name_en %></td>
        <td><%= shop_data[i].shop_account_id %></td>
        <td><%= shop_data[i].updated_at %></td>
        <td><a href="/edit/<%= shop_data[i].shop_id %>">更新する</a></td>
      </tr>
      <% }; %>

企業一覧画面ではforEachを使いましたが、こちらは for 文で表記しました。これで、企業に属する店舗の一覧を表示させることができました。

小技とつまずいたポイント

・出力したいデータが正しいかどうかをHTML(.ejs)でみたい
<%- JSON.stringify(shop_data) %>と記述することで見られます!

・JavaScriptのデータは配列[ ]の中にオブジェクト{ }をもつことができる。
→ JavaScriptは [{ name:aaa, email:xxx@yyyy}, { name:bbb, email:yyy@xxxx}, { }....]とできるようです。

・forとif文を一気に記述するのではなく、一つづつ書くこと
→ 処理を一気に書こうとして、ほしいデータをなかなか出すことが出来ませんでした。落ち着いて出力されたデータを見ながら、一つづつ解決するほうが結果早いですね。

・配列から要素を取り出す方法
→ iをつけることに、なかなか気がつけませんでした。

まとめ

JavaScriptを勉強しはじめて3週間ほどですが、MySQLのデータを加工を実施しました!空の配列を作って、ほしいデータを作成をすることは初めての作業でしたが、なんとかうまく出来たので良かったです。途中で、配列なのか連想配列なのか迷ったりしたため中々答えにたどり着けませんでした。これが初学者の方のためになればと思います。

また、一部未完成・不十分な記述がありますので、ご教示いただけると幸いです!

以上

参照

Express.js(node.js)からMySQLへの接続とCRUD操作
[Node.js][Express]リクエストからパラメータを取得する・POSTされたデータを取得する
for 文と push メソッドを使って配列要素を複数生成