node.jsだけで掲示板アプリを作ってみる(後編)
「node.jsだけで掲示板アプリを作ってみる(前編)」のつづきです。
全体構造がわかるようにapp.js というファイル1つで実現します。
1. 前回までのまとめ
- node.js でhttpサーバー起動、レスポンスを返す
- URL / 及び /up で表示を変える(ルーティング)
- HTMLを表示する。(スタイルとHTMLタグ)
app.js
const http = require('http'); // httpサーバーmodule const hostname = 'localhost'; // ホスト名 const port = 3000; // port番号 // httpサーバーの定義 const server = http.createServer((req, res) => { // 全リクエストを処理 res.statusCode = 200; // http ステータスコード res.setHeader('Content-Type', 'text/html; charset=UTF-8'); // HTMLを返す 日本語返すので charsetもセット // 全ページ共通HTMLヘッド res.write('<html><head><title>掲示板</title><style>* {box-sizing:border-box;}</style></head><body style="position:relative;height:100%;">'); res.write('<header style="border:1px solid #888;padding:40px;">掲示板</header>'); res.write('<nav><ul><li><a href="/">トップ</a></li><li><a href="/up">投稿</a></li></nav>'); // ルーティング (url により振り分ける) switch (req.url) { // req.url にリクエストされたパスが入る case '/': res.write('<h1>トップページです</h1>\n'); break; case '/up': res.write('<h1>投稿ページです</h1>\n'); break; default: res.write('<h1>その他のページです</h1>\n'); break; } // 全ページ共通HTMLフッター res.write('<footer style="position:absolute;bottom:0;width:100%;border:1px solid #888;text-align:center;padding:20px;">フッター</footer>\n'); // 共通のフッター res.end('</body></html>'); // res.endでもコンテンツを返せる }); // サーバー起動 server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); });
前回までのこれを
app.js
const http = require('http'); // httpサーバーmodule const hostname = 'localhost'; // ホスト名 const port = 3000; // port番号 // httpサーバーの定義 const server = http.createServer((req, res) => { // 全リクエストを処理 res.statusCode = 200; // http ステータスコード res.setHeader('Content-Type', 'text/html; charset=UTF-8'); // HTMLを返す 日本語返すので charsetもセット // 全ページ共通HTMLヘッド(下で関数化 header(req,res)) // ルーティング (url により振り分ける) switch (req.url) { // req.url にリクエストされたパスが入る case '/': topPage(req,res); // トップページ用関数 break; case '/up': upPage(req,res); // 投稿ページ用関数 break; default: notFoundPage(req,res); // その他ページ用関数 Not Foundページにします break; } // 全ページ共通HTMLフッター(下で関数化 footer(req,res)) }); // サーバー起動 server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); }); /////////////////////// 各ページの機能 ////////////////////////// // ヘッダ function header(req,res) { res.write('<html><head><title>掲示板</title><style>* {box-sizing:border-box;}</style></head><body style="position:relative;height:100%;">'); res.write('<header style="border:1px solid #888;padding:40px;">掲示板</header>'); res.write('<nav><ul><li><a href="/">トップ</a></li><li><a href="/up">投稿</a></li></nav>'); } // フッタ function footer(req,res) { res.write('<footer style="position:absolute;bottom:0;width:100%;border:1px solid #888;text-align:center;padding:20px;">フッター</footer>\n'); // 共通のフッター res.end('</body></html>'); // res.endでもコンテンツを返せる } // トップページ function topPage(req,res) { header(req,res); res.write('<h2>トップページ</h2>\n'); footer(req,res); } // 投稿ページ function upPage(req,res) { header(req,res); res.write('<h2>投稿します</h2>\n'); footer(req,res); } // その他のページ function notFoundPage(req,res) { header(req,res); res.write('<h2>ページはありません</h2>'); footer(req,res); }
やったこと
- ページ別に処理を関数化
- ヘッダとフッタも関数化
- その他のページはNot Foundにする予定
2. 投稿ページ
投稿用関数にフォーム表示
app.js
// 投稿ページ function upPage(req,res) { res.write('<h2>投稿します</h2>\n'); }
これを
// 投稿ページ function upPage(req,res) { res.write('<h2>投稿します</h2>\n'); res.write('<form action="/up" method="post"><div><textarea style="width:80%;height:100px"></textarea><div><div><input type="submit" value="投稿"></div></form>'); }
やったこと
- formを表示するようにします。
- ここではおなじURL(/up)にsubmit するようにします
3. 投稿データを保存する
サーバー起動中のメモリ上に掲示板データを保存するようにします。 /up では httpリクエストメソッドがGETのときはformを表示し、POSTのときは投稿データを保存するようにします。
この方式ではサーバーを落とすとデータも消えます。立ち上げている間だけ蓄積するようにするコードです。
app.js
// 投稿ページ var posts = []; // 掲示板データ function upPage(req,res,data) { header(req,res); // リクエストメソッドで処理を変える // GETの処理 if (req.method === 'GET') { res.write('<h2>投稿します</h2>\n'); res.write('<form action="/up" method="post"><div><textarea name="kakikomi" style="width:80%;height:100px"></textarea><div><div><input type="submit" value="投稿"></div></form>'); footer(req,res); return; } // POSTの処理 if (req.method === 'POST') { // POSTデータを受け取る(共通) // dataイベントでPOSTされたデータがちょっとずつ来るのでbodyに蓄積する let body = []; req.on('data', (chunk) => { body.push(chunk); }).on('end', () => { body = Buffer.concat(body).toString(); //パースする const querystring = require('querystring'); parsedBody = querystring.parse(body); if (parsedBody.kakikomi) { posts.push(parsedBody.kakikomi); res.write('<h2>投稿しました</h2>\n'); res.write(decodeURIComponent(parsedBody.kakikomi)); // htmlescape 省略 } footer(req,res); }); } }
やったこと
- /up アクセス時のGET時とPOST時の処理分け
- POST時にreqのイベントでPOST値を受け取る
- POST値をパース
- 投稿データを内部配列に格納
ブラウザでの動作
- /up にアクセス
- フォームに書き込み送信
- 内部のposts配列に書き込みを追加
- 「投稿しました」と書き込んだ文字列を表示する
4. 投稿された書き込みを表示する
トップページで書き込まれた内容をリスト表示します。
app.js
// トップページ function topPage(req,res) { header(req,res); res.write('<h2>トップページ</h2>\n'); footer(req,res); }
これを
// トップページ function topPage(req,res) { header(req,res); res.write('<h2>トップページ</h2>\n'); res.write('<ul>'); for(let row of posts) { res.write('<li>'+row+'</li>\n'); // htmlescapeは省略してます } res.write('</ul>'); footer(req,res); }
やったこと
- グローバル配列のpostsをループして表示
ブラウザでの動作
- /up で書き込みを投稿後トップページで一覧を表示する
- /up で投稿を繰り返すと追加されていく。
- / トップでリストを確認できる
- サーバー再起動でデータは消えます。
5. Not Found ページの実装
/と/up 以外のURLでリクエストされた場合はNot foundとします
app.js
// その他のページ function notFoundPage(req,res) { res.write('<h2>ページはありません</h2>'); }
これを
// その他のページ function notFoundPage(req,res) { res.statusCode = 404; // http ステータスコードを返します header(req,res); res.write('<h2>ページはありません</h2>'); footer(req,res); }
やったこと
- レスポンスのステータスコードを404にする
5. まとめ
できたもの
- トップページ(/)で投稿データのリストを表示
- 投稿ページ(/up)でフォームを表示する
- 投稿アップ(/up)のメソッドPOSTでデータを取り込む
- CSSとHTMLもコミコミなので分けたくなる。→ フレームワーク、テンプレート利用などへの進化
- データもどこかに保存したい → データベース活用への進化
以上node.jsだけの1ソース掲示板アプリでした。
付録. 全ソース
app.js
const http = require('http'); // httpサーバーmodule const hostname = 'localhost'; // ホスト名 const port = 3000; // port番号 // httpサーバーの定義 const server = http.createServer((req, res) => { // 全リクエストを処理 res.statusCode = 200; // http ステータスコード res.setHeader('Content-Type', 'text/html; charset=UTF-8'); // HTMLを返す 日本語返すので charsetもセット // ルーティング (url により振り分ける) switch (req.url) { // req.url にリクエストされたパスが入る case '/': topPage(req,res); // トップページ用関数 break; case '/up': upPage(req,res); // 投稿ページ用関数 break; default: notFoundPage(req,res); // その他ページ用関数 Not Foundページにします break; } }); // サーバー起動 server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); }); /////////////////////// 各ページの機能 ////////////////////////// // ヘッダ function header(req,res) { res.write('<html><head><title>掲示板</title><style>* {box-sizing:border-box;}</style></head><body style="position:relative;height:100%;">'); res.write('<header style="border:1px solid #888;padding:40px;">掲示板</header>'); res.write('<nav><ul><li><a href="/">トップ</a></li><li><a href="/up">投稿</a></li></nav>'); } // フッタ function footer(req,res) { res.write('<footer style="position:absolute;bottom:0;width:100%;border:1px solid #888;text-align:center;padding:20px;">フッター</footer>\n'); // 共通のフッター res.end('</body></html>'); // res.endでもコンテンツを返せる } // トップページ function topPage(req,res) { header(req,res); res.write('<h2>トップページ</h2>\n'); res.write('<ul>'); for(let row of posts) { res.write('<li>'+row+'</li>\n'); // htmlescapeは省略してます } res.write('</ul>'); footer(req,res); } // 投稿ページ var posts = []; // 掲示板データ function upPage(req,res) { header(req,res); // リクエストメソッドで処理を変える // GETの処理 if (req.method === 'GET') { res.write('<h2>投稿します</h2>\n'); res.write('<form action="/up" method="post"><div><textarea name="kakikomi" style="width:80%;height:100px"></textarea><div><div><input type="submit" value="投稿"></div></form>'); footer(req,res); return; } // POSTの処理 if (req.method === 'POST') { // POSTデータを受け取る(共通) // dataイベントでPOSTされたデータがちょっとずつ来るのでbodyに蓄積する let body = []; req.on('data', (chunk) => { body.push(chunk); }).on('end', () => { body = Buffer.concat(body).toString(); //パースする const querystring = require('querystring'); parsedBody = querystring.parse(body); if (parsedBody.kakikomi) { posts.push(parsedBody.kakikomi); res.write('<h2>投稿しました</h2>\n'); res.write(decodeURIComponent(parsedBody.kakikomi)); // htmlescape省略 } footer(req,res); }); } } // その他のページ function notFoundPage(req,res) { res.statusCode = 404; // http ステータスコードを返します header(req,res); res.write('<h2>ページはありません</h2>'); footer(req,res); }