December 2015

Volume 30 Number 13

働くプログラマ - MEAN あれこれ: Express の入力

Ted Neward | December 2015

Ted Neward「ノーディスト」の皆さん、おかえりなさい (「ノーディスト」は、Node.js を日常的に使う人々に、このコラムで付けた準公式の愛称です。この呼び方がお気に召さない方は、メールか Twitter でご意見をお寄せください。ちなみに、候補に挙がったあと 2 つの愛称は「ノードラーティ」と「ノードフェラトゥ」です)。

前回は、Web API のエンドポイントの形式で、アプリケーションにいくつかの出力機能を含め、URL の一部として指定される任意の "id" を利用して、人物のコレクションや個人を取得できるようにしました (人物のコレクションは、このアプリケーションのリソースで、ある種の人物データベースを構築しています)。今回は入力処理です。具体的には、新しい人物のシステムへの追加、システムからの人物の削除、既存の人物の更新を行います。ある意味、入力もアプリケーションの "単なる" 新しい URL エンドポイントですが、今回は新しいテクニックをいくつか紹介する予定です。

前回と同様、このシリーズの一環として作成した最新かつ最高のコードに関心のある方は、シリーズ最新のコードを収めた Microsoft Azure のサイト (msdn-mean.azurewebsites.net) を参照してください。発行日を考えると、このコラムの内容はサイトで使用しているものとおそらく一致しません。一致しないというよりはむしろ、サイトの内容の方が新しいので、その後の動向を掴むにはサイトの方が役に立ちます。

前回のコラムを振り返ってみると、コードはデータベース内の既存の人物を表示できても、それらを変更する方法は用意しませんでした。通常、どのようなオンライン システムでも変更機能が重要なので、「R」に「CUD」を追加して「CRUD」を完成させましょう。

削除の実装

まず実装するのは、最も簡単な「CRUD」における「D」、つまり「Delete」(削除) です。Express でルートのセットアップに必要なのは、以下のように、前回使用した get の代わりに delete を使用することだけです。

app.delete('/persons/:personId', deletePerson);

前回のコラムをおさらいしましょう。「:personId」は、関連付けられている関数 (deletePerson) における Express の「req」(要求) オブジェクトで使用可能になるパラメーターです。これは personId ミドルウェア関数 (こちらも前回説明しました) によって選択されるため、どの人物をシステムから削除するかがわかります。

deletePerson の実装は比較的簡単です。lodash パッケージの remove メソッドを使用して、インメモリ データベースを検索し、記述されているその人物を削除します (図 1 参照)。

図 1 lodash パッケージの remove メソッドの使用

var deletePerson = function(req, res) {
  if (req.person) {
    debug("Removing", req.person.firstName, req.person.lastName);
    _.remove(personData, function(it) {
      it.id === req.person.id;
    });
    debug("personData=", personData);
    var response = { message: "Deleted successfully" };
    res.status(200).jsonp(response);
  }
  else {
    var response = { message: "Unrecognized person identifier"};
    res.status(404).jsonp(response);
  }
};

personId ミドルウェアは、:personId (personId の値なら何でも) を選択し、データベース内で適切な人物のオブジェクトを見つけて、着信する要求 (「req」) オブジェクトに個人のプロパティとして追加します。したがって、req.person がなければ、その ID の人物はデータベース内に見つからなかったことになります。ただし、応答を送信するときは、前回登場した組み込みの JSON.stringify メソッドを経由して結果を JSON に変換するのではなく、JSON 形式にデータを変換する jsonp メソッドを使用します。正確に言うと、JSON をブラウザーに再送信するのに優れ、かつ安全な方法として広く認識されている JSONP (JSON with padding) 形式に変換します。メソッドが「チェーン」を呼び出している方法に注目します。つまり、1 行の滑らかなコードで、人物が正常に削除されたというメッセージを含む JSON 応答と 200 (または、人物の ID が認識されていないというメッセージを含む JSON 応答と 404) を送信できます。

データベースへの追加

次は、データベースに人物を追加できるようにします。これもかなり簡単なルーティングになります。昔から Web API の支持者は、データベースに挿入する方法として、リソース コレクションの URL (/persons) に POST を使用することを推奨しているため、次のようにしてみます。

app.post('/persons', insertPerson);

特におもしろみはありません。それどころか、新たな問題が発生します。人物をシステムに挿入するためには、着信した要求からその人物のデータの JSON を取り出す必要があります。1 つは、req.body パラメーターを使用して要求本文全体を取得する方法ですが、取得した後、JSON を解析してストレージに適したオブジェクトにするという、面倒かつ少し危険な作業を実行することになります。こういう場合に Node.js コミュニティで重宝しているのが、ライブラリの豊富なコレクションです。こうした解析には、body-parser という優れたライブラリがあります。(package.json と同じディレクトリで「npm install body-parser」を実行して) このライブラリをインストールして参照し、以下のようにして、このライブラリを使用することを、Express アプリケーション オブジェクトに指示できます。

// Load modules
  var express = require('express'),
  bodyParser = require('body-parser'),
  debug = require('debug')('app'),
  _ = require("lodash");
// Create express instance
var app = express();
app.use(bodyParser.json());

先ほどの :personId に関する話に戻りましょう。パイプラインの一環としてちょっとした作業を暗黙のうちに実行するミドルウェア関数を Express で作成するにはどうすればよいでしょう。そこで、body-parser ライブラリの出番です。さまざまな形式で要求本文を処理するために、多くの「フック」(これ以外にいい言葉が思いつきません) をインストールします。今回は JSON を解析するよう指示しますが、body-parser は URL のエンコーディングもサポートできます。そのため、(たとえば CSV を解析しやすくするために) すべてを巨大な文字列として解析します。または (おそらく、着信データがなんらかのバイナリというのが理由で) Node.js Buffer の中にあらゆるものを「手を加えずに」取り込みます。現在必要とされるほとんどのものは JSON で対処されているため、body-parser があれば十分です。

実のところ、insertPerson 関数は次のようにかなり簡単なものです。

var insertPerson = function(req, res) {
  var person = req.body;
  debug("Received", person);
  person.id = personData.length + 1;
  personData.push(person);
  res.status(200).jsonp(person);
};

(一意 id を生成するのに大きな手間が掛かることがありますが、最終的に MongoDB を使用することが目的なので、あまり気にしないようにしてください)

コードは、insertPerson から戻ると、新しい id フィールドなど、挿入されたばかりの完全な Person オブジェクトを送信します。これはまったく標準の表記法ではありませんが、今回作成したコードでは便利でした。こうするとクライアントは、送信されたエンティティ (この場合は id フィールド) の検証/訂正/サーバー側の拡張が追加で行われたことを認識します。

POST 方法

ちなみに、(従来の Web アプリケーションとは対照的に) この方法での Web API の構築に関して、興味深い点があります。つまり、簡易テストを実行するシンプルなテクニックの一部が使えません。たとえば、insertPerson 関数が機能しているかどうかを簡単にテストする方法を考えてみましょう。確かに、Node.js ではテストの自動化が大量にサポートされ (これは今後のコラムで扱う予定) ていますが、現時点ではブラウザー プラグイン (Chrome の "Postman" など) を使うのが最も簡単です。コマンドラインの熱烈なファンならば、OS X と多くの Linux イメージにプレインストールされている cURL フリーウェア ユーティリティを使うこともできます。どちらのツールも気が進まない場合は、これ以外にも、アドホックなものから自動のものまで多くのツールがあり、そのいずれもが、ありとあらゆる複雑さに対処します。中でも、お気に入りのツールの 1 つは Runscope (runscope.com) です。これは、24 時間 365 日稼働しているものに対し、API エンドポイントの自動テストを実行するクラウドベースのシステムです。

どのツールを使用するにしても、合わないと思えばすぐに取り替えてください。文字どおり、数千個ものツールが存在しています。

更新の実装

最後に、アプリケーションからデータベース内の人物を更新できるようにします。多くの点で、これは削除 (データベース内の該当人物の発見) と挿入 (着信 JSON の解析) を複雑に組み合わせたものになりますが、インストール済みのミドルウェアはそれらの多くに既に対処しています (図 2 参照)。

図 2 データベース内の既存の人物を更新

var updatePerson = function(req, res) {
  if (req.person) {
    var originalPerson = req.person;
    var incomingPerson = req.body;
    var newPerson = _.merge(originalPerson, incomingPerson);
    res.status(200).jsonp(newPerson);
  }
  else {
    res.status(404).jsonp({ message: "Unrecognized person identifier" });
  }
};
// ...
app.put('/persons/:personId', updatePerson);

図 2 で重要な点は、lodash パッケージの merge メソッドを使用していることです。2 つ目のパラメーターのプロパティを列挙して、1 つ目のパラメーターで同じ名前のプロパティを上書きするか、それを追加します。こうすれば、1 つ目のオブジェクトの破棄と再作成を実行することなく、2 つ目のパラメーターの属性を 1 つ目の属性に簡単にコピーできます。今回はこれが重要になります。1 つ目のオブジェクトは、当座しのぎのデータベースの役割を果たす personData 配列内にあるためです。

好奇心旺盛な方のために、if ステートメントの true 分岐は、次のように 1 行のコードに凝縮できます。

var updatePerson = function(req, res) {
  if (req.person) {
    res.status(200).jsonp(_.merge(req.person, req.body));
  }
  else {
    res.status(404).jsonp({ message: "Unrecognized person identifier" });
  }
};

これが読みやすいかどうかは、皆さんの判断しだいです。

まとめ

数えていた方がいるかもしれませんが、本連載 2 回で生み出した app.js ファイルのコードは、今や 100 行に達し (約 6 行のコメント含む)、インメモリ データベースに対する完全な CRUD Web API をサポートするようになりました。100 行にしては上出来です。ただし、やるべきことはまだ残っていて、システムがインメモリ データベースではなく、MongoDB を使用するようにする必要があります。この重要な CRUD Web API を既に使用しているクライアントをどれも機能不全にしないためには、テストを実行しなくてはなりません。手作業で反復テストを実行したいと考える開発者はいないと思います。したがって次回は、クライアントの観点からシンプルかつシームレスに MongoDB へと移行できるよう、自動テストの追加について考えます。それまでの間、コーディングを楽しんでください。


Ted Neward は、ポリテクノロジーに関するシアトルのコンサルティング サービス会社 iTrellis で CTO を務めています。これまでに 100 本を超える記事を執筆している Ted は、F# MVP および INETA 講演者で、さまざまな書籍を執筆および共同執筆しています。仕事への協力を依頼する場合、連絡先は ted@tedneward.com (英語のみ) です。また、tedneward.com (英語) でブログを公開しています。

この記事のレビューに協力してくれた技術スタッフの Shawn Wildermuth に心より感謝いたします。