Node.jsで詳細ページを作ってみよう - osamu38/node-express-curriculum GitHub Wiki

@@ -1,336 +0,0 @@

Node.jsで詳細ページを作ってみよう

目次

  • 詳細ページを表示させてみよう
  • メッセージを投稿しよう
  • メッセージを表示させてみよう
  • トップページからリンクさせよう

詳細ページを表示させてみよう

データベースとの連携はなんとなく理解できたと思うので、ここから詳細画面を作っていきましょう。

新規ページを作成する場合は最初にapp.jsでルーティングの設定します。

app.jsを以下のように書き換えます。

// 中略
var users = require('./routes/users');
var boards = require('./routes/boards'); // ←追加

var app = express();
// 中略



// 中略
app.use('/users', users);
app.use('/boards', boards); // ←追加

// catch 404 and forward to error handler
app.use(function(req, res, next) {
// 中略

このあとに作るroutes/boards.js/boardsというURLに紐付ける処理をしています。

routes/boards.jsを作成します。

var express = require('express');
var router = express.Router();
var connection = require('../mysqlConnection');

router.get('/:board_id', function(req, res, next) {
  var boardId = req.params.board_id;
  var query = 'SELECT * FROM boards WHERE board_id = ' + boardId;
  connection.query(query, function(err, board) {
    res.render('board', {
      title: board[0].title,
      board: board[0]
    });
  });
});

module.exports = router;

routes/boards.js内の処理でrouter.get('/:board_id', ...といった記述がありますが、どのようなURLに紐づくかを説明していきます。

メソッド ルーティング(app.js) ルーティング(個別のjs) URL
GET app.use('/', routes); router.get('/', localhost:3000
POST app.use('/', routes); router.post('/', localhost:3000
POST app.use('/boards', boards); router.post('/:board_id', localhost:3000/boards/1
GET app.use('/boards', boards); router.get('/:board_id', localhost:3000/boards/1

このようにapp.jsと個別のjsの両方を用いてルーティングしているのがわかると思います。

そしてこの:board_idの取得方法がreq.params.board_idです。

フォームから送られた場合はreq.body.board_idとなるので、間違えないようにしましょう。

ちなみにboardは1件しかデータが返ってこない場合でも必ず配列で返ってくるので、board[0]で展開して渡すとView側で使いやすくなります。

views/board.ejsを作成します。

<!DOCTYPE html>
<html lang="ja">
<head>
<title><%= title %></title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="format-detection" content="telephone=no">
<link rel='stylesheet' href='/stylesheets/style.css'>
</head>
<body>
<div class="wrapper">
    <p class="main-title"><%= title %></p>
    <a href="/" class="btn">トップへもどる</a>
</div>
</body>
</html>

これを作成したあとに、http://localhost:3000/boards/1にアクセスしてみましょう。

このような画面が出てきたらおkです。こちらが詳細ページとなります。

メッセージを投稿しよう

ではviews/board.ejsにメッセージを投稿するフォームを設置しましょう。

views/board.ejsを以下のように書き換えます。

    <!-- 中略 -->
    <p class="main-title"><%= title %></p>
    <form action="/boards/<%= board.board_id %>" method="post" class="board-form">
        <input type="text" name="message" class="input" required>
        <button type="submit" class="submit">投稿</button>
    </form>
    <a href="/" class="btn">トップへもどる</a>
    <!-- 中略 -->

routes/boards.jsを以下のように書き換えます。

var express = require('express');
var router = express.Router();
var moment = require('moment'); // 追加
var connection = require('../mysqlConnection');

router.get('/:board_id', function(req, res, next) {
  var boardId = req.params.board_id;
  var query = 'SELECT * FROM boards WHERE board_id = ' + boardId;
  connection.query(query, function(err, rows) {
    res.render('board', {
      title: rows[0].title,
      board: rows[0]
    });
  });
});

router.post('/:board_id', function(req, res, next) {
  var message = req.body.message;
  var boardId = req.params.board_id;
  var createdAt = moment().format('YYYY-MM-DD HH:mm:ss');
  var query = 'INSERT INTO messages (message, board_id, created_at) VALUES ("' + message + '", ' + '"' + boardId + '", ' + '"' + createdAt + '")';
  connection.query(query, function(err, rows) {
    res.redirect('/boards/' + boardId);
  });
});

module.exports = router;

前に作った投稿部分とほぼ同じロジックを使ってるので、説明を割愛します。 では実際にPOSTしてみましょう。

フォームからメッセージをPOSTすると...

ちゃんとメッセージがデータベースに格納されていますね!ではこのメッセージを画面に表示させてみましょう。

メッセージを表示させてみよう

routes/boards.jsを以下のように書き換えます。

// 中略
router.get('/:board_id', function(req, res, next) {
  var boardId = req.params.board_id;
  var getBoardQuery = 'SELECT * FROM boards WHERE board_id = ' + boardId;
  var getMessagesQuery = 'SELECT *, DATE_FORMAT(created_at, \'%Y年%m月%d日 %k時%i分%s秒\') AS created_at FROM messages WHERE board_id = ' + boardId;
  connection.query(getBoardQuery, function(err, board) {
    connection.query(getMessagesQuery, function(err, messages) {
      res.render('board', {
        title: board[0].title,
        board: board[0],
        messageList: messages
      });
    });
  });
});
// 中略

routes/boards.jsではGETした場合の処理を書いていましたが、今回その処理の部分に変更を加えていきたいと思います。

なぜかconnection.query();の中にconnection.query();がありますね。気持ち悪いですね。

本来であれば

  var board, messages;
  connection.query(getBoardQuery, function(err, board) {
    board = board;
  });
  connection.query(getMessagesQuery, function(err, messages) {
    messages = messages;
  });
  res.render('board', {
    title: board[0].title,
    board: board[0],
    messageList: messages
  });

といった形で処理をするとわかりやすいのですが、実はconnection.query();非同期処理なのです。なのでこのようには書けません。

では非同期処理とはなんなのか

Webブラウザは基本的に、JavaScriptコードを実行するとき、コードを上から順に1行ずつ実行します。 また、関数を呼び出すと、その関数の実行が終了するまで(return文によって呼び出し元の関数に戻ってくるまで)次の行には進みません。 さらに、JavaScriptコード実行中は他の処理は待ち状態になります。たとえば、マウスクリックイベントをイベントハンドラで処理している間にユーザーがキー入力を行っても、画面に入力した文字は反映されず、キー入力に対するイベントハンドラも実行されません(マウスクリックのハンドラの実行が終わった後に実行されます)。

...よくわかりませんね。

JavaScriptは上から順番に処理されます。

  var board, messages; // ①
  connection.query(getBoardQuery, function(err, board) { // ②
    board = board; // ③
  });
  connection.query(getMessagesQuery, function(err, messages) { // ④
    messages = messages; // ⑤
  });
  res.render('board', { // ⑥
    title: board[0].title,
    board: board[0],
    messageList: messages
  });

みなさんはこのような順番で処理されると思いますが、非同期処理だとこうなりません。

  var board, messages; // ①
  connection.query(getBoardQuery, function(err, board) { // ②(ひとまず処理を実行。いつ処理が終わるかわからない)
    board = board; // ⑤か⑥(いつ処理が終わるかわからないので、⑤か⑥かわからない)
  });
  connection.query(getMessagesQuery, function(err, messages) { // ③(ひとまず処理を実行。いつ処理が終わるかわからない)
    messages = messages; // ⑤か⑥(いつ処理が終わるかわからないので、⑤か⑥かわからない)
  });
  res.render('board', { // ④
    title: board[0].title, // ④の時点ではboard[0].titleはundefined
    board: board[0], // ④の時点ではboard[0]はundefined
    messageList: messages // ④の時点ではmessagesはundefined
  });

②と③が非同期処理です。そのためいつ処理が終わるかわかりません。結果として④の時点ではboard[0]messagesには値が格納されていないため、エラーが起きてしまいます。

では次にもともとのソースコードを見てみましょう。

  connection.query(getBoardQuery, function(err, board) { // ①
    connection.query(getMessagesQuery, function(err, messages) { // ②(①の処理がいつ終わるかわかんないけど、次の処理はこれ)
      res.render('board', { // ③(②の処理がいつ終わるかわかんないけど、次の処理はこれ)
        title: board[0].title,
        board: board[0], // ①の処理は終わっているのでboard[0]は存在する
        messageList: messages // ②の処理は終わっているのでmessagesは存在する
      });
    });
  });

といった感じで実装していたわけですね。非同期処理はネスト(インデント)が深くなりやすいのですが、これを解決する方法はたくさんあります。今後もJavaScriptをやる限り非同期処理とは向き合っていかなければいけないので、苦手意識をもたずに頑張りましょう。

詳しく知りたい人はこちら

Viewに対してデータを渡すことはできたので、次にViewファイルをいじっていきましょう。

views/board.ejsを以下のように書き換えます。

    <!-- 中略 -->
    <p class="main-title"><%= title %></p>
    <% if (messageList.length) { %>
        <div class="white-bg">
            <ul class="main-list">
                <% messageList.forEach(function(messageItem) { %>
                    <li class="main-list__item">
                        <div class="message">
                            <p class="message__title"><%= messageItem.message %></p>
                            <p class="message__date"><%= messageItem.created_at %></p>
                        </div>
                    </li>
                <% }); %>
            </ul>
        </div>
    <% } %>
    <form action="/boards/<%= board.board_id %>" method="post" class="board-form">
    <!-- 中略 -->

これもトップページで作ったものと同じような実装ですが、今回はif文を使っています。

<% if (messageList.length) { %>

これはメッセージが1件でもある場合のみリストを表示させるような処理をしています。

他にも「ログインしているユーザー」や「ブロックされている」などで場合分けが可能です。

Viewも完成したので、さっそくlocalhost:3000/boards/1にアクセスしてみましょう。

メッセージが表示されるようになりました!これで詳細ページの実装は完了です。

トップページからリンクさせよう

URLに直接アクセスすればページを表示することができますが、出来ればボードをクリックしたらリンクする方が便利なのでつないでみましょう。

views/index.ejsを以下のように書き換えます。

                <!-- 中略 -->
                <li class="main-list__item">
                    <a href="/boards/<%= boardItem.board_id %>" class="board">
                        <p class="board__title"><%= boardItem.title %></p>
                        <p class="board__date"><%= boardItem.created_at %></p>
                    </a>
                </li>
                <!-- 中略 -->

これでリンクを設定することができました。試しに押してみると遷移すると思います。

ここまででNode.jsの基礎中の基礎編は完了です。

前のページ:Node.jsでデータベースからデータを取得して表示させてみよう

次のページ:Node.jsで会員登録システムを導入しよう

⚠️ **GitHub.com Fallback** ⚠️