JavaScriptだけでWebサイト開発

node.js + Express + mongodb系 + React でサービスを作るメモ

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);
}
©ichi-bit