特集:Windows開発者のためのNode.js入門

勢いで始めてみるNode.js Webアプリ開発

デジタルアドバンテージ 一色 政彦
2012/06/15
Page1 Page2

初めてのNode.jsによるWebアプリ開発

ひな型ファイルの確認

 Node.jsのテンプレートから自動作成されたひな型のファイル群を確認してみよう。

 WebMatrix 2の左下にあるメニューで[ファイル]を選択すると、左上に現在のプロジェクトに含まれるファイル群がツリー表示される(次の画面を参照)。

Node.jsのテンプレートから自動作成されたひな型のファイル群

 一番重要なのは、「server.js」というファイルである。そのコード内容については後で詳しく説明するが、その前に主なファイルの内容を箇条書きで紹介しておく。

  • server.js: Webサーバ(HTTPサーバ)を動かす。
  • HTMLPage.html: ひな型のHTMLファイル。現時点で使われていないが、後述のサンプルで使用する。
  • web.config: IIS/IIS Expressの背後でNode.js用プロセスを実行するための設定が記述されている。この中で、「server.js」ファイルがNode.jsアプリケーションとして、またpublicフォルダ内はスタティックとして宣言されている。
  • robots.txt: 検索エンジンのクローラ向けのファイル。中身は空。
  • favicon.ico: サイトのアイコン。仮のもの。
  • publicフォルダ: この中のファイルはスタティック・ファイルとして配信できるので、.jsファイルや.cssファイルを配置するとよい。配置したファイルには、「public」というフォルダ名を入れずに、「http://localhost:13506/example.txt」のような形でアクセスできる。
  • server.js.debub/server.js.logsフォルダ: ひな型状態では本来は存在しない(筆者が少しデバッグ関連を試したので生成された)。Node.jsで使うデバッグやログ用のフォルダで、Webアプリ内で使うものではない。

 つまり、付属するファイルはたくさんあるが、ひな型のNode.jsプログラミング関連のファイルは「server.js」ファイルしかない。これを開くと、次のような5行のコードになっている。

var http = require('http');

http.createServer(function (req, res) {
 
  res.writeHead(200, { 'Content-Type': 'text/html' });
  res.end('Hello, world!');
 
}).listen(process.env.PORT || 8080);
最もシンプルなWebアプリのコード(server.js)

 コード内容を説明していこう。

「var http = require('http');」

 Node.jsでは「モジュール」という形で、さまざまな機能を読み込める。ここでは、「http」という文字列を引数に指定したrequire関数を呼び出すことで、httpモジュールを読み込んでいる。

「http.createServer(...)」

 httpモジュールのcreateServer関数を呼び出して、http.Serverクラスのオブジェクトを作成している。

「function (req, res) {...}」

 createServer関数の引数として、requestイベントを処理するコールバック関数が指定されている。「req」は、HTTPリクエストを表すhttp.ServerRequestクラスのインスタンス、「res」は、HTTPレスポンスを表すhttp.ServerResponseクラスのインスタンスである。

「res.writeHead(200, { 'Content-Type': 'text/html' });」

 resオブジェクトのwriteHead関数を使って、ステータス・コード「200」とHTTPヘッダ「Content-Type: text/html」を引数に指定して、レスポンス・ヘッダを送信している。

「res.end('Hello, world!');」

 resオブジェクトのend関数を使って、「Hello, world!」というデータの送信とともに、レスポンスのヘッダとボディの送信完了を伝達している。

「.listen(process.env.PORT || 8080);」

 createServer関数の戻り値はhttp.Serverオブジェクトである。そのlisten関数を使って、指定されたポート番号(「process.env.PORT」はユーザー環境のポート番号)でのコネクションの受け入れを開始する。つまり、この時点から、上記のプログラム内容が動作することになる。

HTMLファイルを動的に表示するサンプルのコード

 以上でひな型は理解できたので、ここからは実際のコーディングを楽しもう。まずは、既存のHTMLPage.htmlファイルをテンプレートとして利用してみることにする。

 Node.jsで使えるモジュールや、その中のクラスと関数を調べるには、以下に示すNode.js公式サイトにあるリファレンスが便利である。

 このリファレンスの目次を見ると、「ファイル・システム」のモジュール(fs)が、ファイルの読み込みに使えそうである。

 HTMLPage.htmlファイルを読み込んで、その内容をそのままレスポンスとして返す処理を書き加える。そのコードは、次のようになった。

var http = require('http')
  , fs = require('fs');

http.createServer(function (req, res) {

  fs.readFile('./HTMLPage.html', 'UTF-8', function(err, data) {

    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end(data);  // 「Hello, world!」から変更

  });

}).listen(process.env.PORT || 8080);
HTMLPage.htmlファイルの内容をそのまま返すWebアプリのコード(server.js)
サンプル用なのでエラー処理は省略した。

 このコードで、追記/修正したのは、主に次の2行だ。

「fs.readFile('./HTMLPage.html', 'UTF-8', function(err, data) { ... });」

 fsモジュールのreadFile関数を使って、「./HTMLPage.html」ファイルの内容を、「UTF-8」文字コードで読み込んでいる。この処理も非同期になっており、処理完了後に第3引数に指定されているコールバック関数が呼び出される。その関数内に(つまりファイル・データの読み込み完了後に)、前述のレスポンス・ヘッダやデータの送信処理を移した。

「res.end(data);」

 resオブジェクトのend関数を使って、ファイル・データの送信とともに、レスポンスのヘッダとボディの送信完了を伝達している。

HTMLファイルを動的に表示するサンプルの実行

 それでは実行して正常に動作するかを確かめてみよう。

 WebMatrix 2の上部にある([ホーム]タブ内の)[実行]ボタンをクリックすると、ローカル環境でNode.jsのWebアプリがブラウザで表示される。HTMLPage.htmlファイルには、必要最小限のタグしか含まれていないので真っ白なページが表示される。HTMLPage.htmlファイルの<body>タグ内に「こんにちは、世界!」などと適当な文字列を書き込んで、[Ctrl]+[S]キーでファイルを保存して、ブラウザをリロードしてみると、その書き込み内容が確かに表示されるのを確認できる。以下の画面はその例だ。

HTMLPage.htmlファイルに何か追記して、ブラウザをリロード
追記した内容が表示された
HTMLPage.htmlファイルをそのまま返すWebアプリの実行例

HTMLファイルをテンプレートとして扱うサンプル

 次に、このHTMLPage.htmlファイルをテンプレートとして利用するためのコードを記述しよう。

 今回のテンプレート・ファイルには、以下のようなプレイスホルダ(=置き換え対象となる目印)を設定できるようにする。

  • 「@@title@@」: Webアプリ・タイトルを設定するためのプレイスホルダ。
  • 「@@content@@」: Webアプリのメイン・コンテンツを設定するためのプレイスホルダ。

 実際のファイル内容は次のとおり。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>@@title@@</title>
  </head>
  <body>
    <h1>@@title@@</h1>
    <div id="content">
      @@content@@
    </div>
  </body>
</html>
各プレイスホルダを設定した場合のHTMLコード(HTMLPage.html)

 ここではテスト用のデモとして、「@@title@@」プレイスホルダを「Thumbla」という文字列に、「@@content@@」プレイスホルダを「<p>ここがメイン・コンテンツです。</p>\r\n<p>コンテンツは動的に生成する予定です。</p>」という文字列に置き換えよう。

 そのためのコードは次のようになる。

……省略……
res.end(data.
  replace(/@@title@@/g, "Thumbla").
  replace("@@content@@",
    "<p>ここがメイン・コンテンツです。</p>\r\n" +
    "<p>コンテンツは動的に生成する予定です。</p>"));
……省略……
HTMLPage.htmlファイルをテンプレートとして扱うコード(server.js)

 このコードは、ファイル・データにおける各プレイスホルダの文字列を、別の文字列に置換しているだけである。「/.../g」はグローバル修飾子「g」が指定された正規表現を意味し、これによって一致するものすべてが置換される。

 この状態でWebアプリを実行すると、次のようになる。

HTMLPage.htmlファイルをテンプレートとして扱う処理の実行例

もっと遊ぼう。Web APIの呼び出し

 筆者が何よりも大好物なのが、Tumblrである。

Tumblr! Tumblr! Tumblr!

 Web APIでサンプルを作る場合、Twitter APIであることがほとんどなので、ここではあえてTumblr APIを使うサンプルを書いてみよう。今回は、筆者のTumblrから「画像」の投稿群だけを入手して、その画像に付随する各種テキストを「@@content@@」の場所に表示する。

 このTumblr APIを使うには、アプリケーション登録が必要になる。これには「Tumblr Applications」のサイトを訪れ、以下の画面の手順で登録を行う。それが終わったら、[OAuth Consumer Key]をコピーする。

[+アプリケーションを登録する]ボタンをクリック
各種登録情報を入力しれ[Register]ボタンをクリックすれば登録は完了
[OAuth Consumer Key]をコピー
HTMLPage.htmlファイルをテンプレートとして扱う処理の実行例

 Tumblr APIの使い方はヘルプ・ページ有志による日本語訳)に記載されている。これによると、投稿データの取得は、次のようなURLでアクセスできることが分かる。

http://api.tumblr.com/v2/blog/{base-hostname}/posts[/type]?api_key={key}&[optional-params=]

 各変数部分は、今回は次のとおりに設定する。

  • {base-hostname}(ブログ名): isshiki.tumblr.com
  • [/type](投稿データ種別): photo
  • {key}(先ほどのOAuth Consumerキー): NFenoqNUHQ82qyN4CJi19U5Clge8bMHTvO9ajrF5zKglvuzb3t
  • [optional-params=](オプションのパラメータ): offset(取得開始位置)やlimit(取得するデータ数。標準は20個)などを指定できるが、今回は指定しない。

 以上の内容をまとめると、

http://api.tumblr.com/v2/blog/isshiki.tumblr.com/posts/photo?api_key=NFenoqNUHQ82qyN4CJi19U5Clge8bMHTvO9ajrF5zKglvuzb3

というURLに対してGETリクエストを投げることで、JSONデータを取得すればよい。

 それでは取りあえず、取得したJSONデータをそのままコンテンツとして表示してみよう。

 Web APIを呼び出すには、httpモジュールのget関数が使える。

 以上を踏まえて、記述したのが次のコードだ。

var http = require('http')
  , fs = require('fs')
  , title = "Thumbla";

http.createServer(function(req, res) {

  fs.readFile('./HTMLPage.html', 'UTF-8', function(err, data) {

    res.writeHead(200, { 'Content-Type': 'text/html' });

    http.get({
      host: 'api.tumblr.com',
      path: '/v2/blog/isshiki.tumblr.com/posts/photo?api_key=NFenoqNUHQ82qyN4CJi19U5Clge8bMHTvO9ajrF5zKglvuzb3t'
    },
    function(clres) {

      var content = "";

      clres.on('data', function(chunk) {

        content += chunk; // 部分データを積み上げる

      }).on('end', function() {

        res.end(data.
          replace(/@@title@@/g, title).
          replace("@@content@@", content));

      });

    }).on('error', function(e) {

      res.end(data.
        replace(/@@title@@/g, title).
        replace("@@content@@",
          "<p>Tumblrからデータがロードできませんでした。</p>"));

    });

  });

}).listen(process.env.PORT || 8080);
外部のWeb APIを呼び出すコード(server.js)

 このコードで追記/修正したのは、主に以下の内容だ。

「http.get(...)」

 get関数の第1引数には、GETリクエスト送信先の情報がオプションとして指定されている。

 第2引数には、レスポンスが返ってきたときに呼び出されるコールバック関数が指定されている。

get関数に指定されたコールバック関数

 その関数の引数として、http.ClientResponseクラスのインスタンスが渡される(先ほどのhttp.ServerResponseクラスとは異なるので注意)。このオブジェクトのon関数により、dataイベントに対するコールバック関数(=「clres.on('data', function(chunk) { ... }」)と、endイベントに対するコールバック関数(=「.on('end', function() { ... }」)が定義されている。このようにイベントに対するリスナーとなるコールバック関数は、on関数で指定できる。

 dataイベントでは、chunk(=ひとかたまりの部分データ)が渡されるが、あくまで一部分なので次々と足し合わせることでデータ全体を完成させる必要があり、コールバック関数ではその処理をしている。

 endイベントは、全データの取得が完了したことを意味するので、このコールバック関数の中で、HTMLページの表示完了処理を行っている。

get関数の戻り値のon関数

 get関数の戻り値は、http.ClientRequestオブジェクトになる(先ほどのhttp.ServerRequestクラスとは異なるので注意)。そのオブジェクトのon関数により、errorイベントに対するコールバック関数(=「.on('error', function(e) { ... }」)が定義されている。

 このコードを実行すると、次のようになる。

外部のWeb APIを呼び出す処理の実行例

 Tumblr APIのJSONデータがそのままテキスト表示されているのが分かる。これを適切なHTMLコードに変換しよう。

JSONデータの変換処理

 ここでは、JavaScirpt標準のJSON.parse関数を使って、JSONデータを解釈してJavaScirptのオブジェクトに変換する。オブジェクトになったデータは、次のコードのように、for文を使ったりして、内部のデータを取り出せばよい。なお、Tumblr APIの写真投稿のJSONデータ構造はマニュアルに記載されている。

……省略……

}).on('end', function() {

  var jdata = JSON.parse(content);
  var sdata = "";
  for(i = 0; i < jdata.response.posts.length; i++) {
    var post = jdata.response.posts[i];
    var orginalPhoto = post.photos[0].alt_sizes[0];
    sdata +=
      "<img src=\"" + orginalPhoto.url +
      "\" /><br>\r\n" + post.caption +
      "<br>\r\n\r\n";
  }

  res.end(data.
  replace(/@@title@@/g, title).
  replace("@@content@@", sdata));

……省略……
JSONデータを変換する処理のコード(server.js)

 「.on('end', function() { ... }」は、前述したendイベントに対するコールバック関数である。この中で、データから写真のURLを取り出して<img>タグに変換し、そのキャプション(=HTMLコード形式)を取り出し、1つのHTMLコードにまとめ、それをWebページ上のメイン・コンテンツとして表示している。

 次の画面は、本稿で作成してきたWebアプリを実行した例だ。Tumblr上の写真が、キャプションとともに20件表示される。

JSONデータを変換する処理の実行例

 取りあえず今回はこれで完成とする。この状態のWebアプリを気軽に試せるように、クラウド上に配置している

Node.js開発に欠かせないデバッガ

 ここまでの説明で徐々にアプリ開発内容が複雑になってきたので、「デバッガがほしい」と思う開発者も少なくないだろう。うれしいことに、WebMatrix 2の拡張機能としてNode.jsのデバッガ(それ以外の機能も搭載されている)が無償提供されているので、最後にこれを紹介しておこう。

 ちなみに先ほどのJSONデータから変換したJavaScriptのオブジェクトの構造から、データを取得する開発を筆者がした際にも、次の画面のようにしてデバッガを使った。デバッガを使えば、開発生産性を高められる。

JSONデータから変換したJavaScriptのオブジェクトの中身をデバッガで調べている例

 この拡張機能をWebMatrix 2に導入するには、以下の手順を参考にしてほしい。

[ホーム]タブ上の[拡張機能]グループの[ギャラリー]ボタンをクリック
[Node Power Tools]を選択して[インストールする]をクリック
インストールが完了すると、[Node]グループに[Debug]ボタンが表示されるのでクリック
ブラウザに表示されるWebページ上でserver.jsファイルを選択して、デバッグしたい位置にブレークポイントを設定する
拡張機能をWebMatrix 2に導入する手順

 本当はもう少しWebアプリに機能を実装したかったが、これだけの機能でも記事がかなり長くなっているので、この後の実装は後編に回す。後編では、Socket.IOへの対応や、Node.js向けWebフレームワークの「express」を勢いだけで使ってみたいと考えている。

 以上、どんな感想を持っただろうか?

 試した人は分かると思うが、WebMatrix 2+Windows Azure Webサイトを使えば、Node.js開発がとても迅速に行える。とにかく手間をかけずにNode.jsによるWeb上のサービスを無償クラウドで一般公開したいという目的なら、非常に便利である。

 「コマンドラインよりもIDEを使いたい」という人なら、今回の内容は試してみる価値があるだろう。筆者はお勧めする。本稿がそのきっかけになればうれしい。end of article

 

 INDEX
  特集:Windows開発者のためのNode.js入門
  勢いで始めてみるNode.js Webアプリ開発
    1.Node.jsによるWebアプリのクラウド&ローカル環境構築
  2.初めてのNode.jsによるWebアプリ開発


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間