演習 - 基本的な Web アプリケーションの作成

完了

ここまでで MongoDB と Node.js を Ubuntu VM にインストールしました。 次に基本的な Web アプリケーションを作成し、動作を確認します。 その過程で AngularJS と Express を組み込む方法を紹介します。

例を利用することは優れた学習方法です。 これから構築する Web アプリケーションでは、基本的な書籍データベースを実装します。 この Web アプリケーションによって、書籍情報を一覧表示したり、新しい書籍を追加したり、既存の書籍を削除したりすることができます。

ここで紹介する Web アプリケーションでは、ほとんどの MEAN スタック Web アプリケーションに適用されるさまざまなコンセプトを見ることができます。 ニーズや関心事に基づき、必要な機能をいろいろ試し、独自の MEAN スタック アプリケーションを構築できます。

書籍 Web アプリケーションは次のようになります。

Screenshot of a web page with a form and submission button.

MEAN スタックの各コンポーネントは次のように動作します。

  • MongoDB によって書籍情報が格納されます。
  • Express.js によって各 HTTP 要求が該当するハンドラーに送信されます。
  • AngularJS によってユーザー インターフェイスとプログラムのビジネス ロジックが接続されます。
  • Node.js によってサーバー側アプリケーションがホストされます。

重要

学習目的のため、ここでは基本的な Web アプリケーションを構築します。 その目的は MEAN スタックをテストし、そのしくみを理解することにあります。 このアプリケーションは安全性が不十分であり、運用環境で使用する準備ができていません。

Express とは何ですか。

ここまでで、MongoDB と Node.js を VM にインストールしました。 MEAN という頭文字の E に相当する Express.js は何のために使用されますか。

Express.js は Node.js のために開発された Web サーバー フレームワークであり、Web アプリケーションの構築プロセスを簡単にします。

Express の主な目的は、要求のルーティングを処理することにあります。 ルーティングとは、特定のエンドポイントへの要求にアプリケーションが応答するしくみです。 エンドポイントはパス、URI、要求メソッド (GET や POST など) から構成されます。 たとえば、/book エンドポイントへの GET 要求に応答するためにデータベースにある全書籍の一覧を提供することがあります。 同じエンドポイントへの POST 要求に応答するために、ユーザーが Web フォームに入力したフィールドに基づき、データベースにエントリを追加することもあります。

この後すぐに作成する Web アプリケーションでは、Express を使用して HTTP 要求をルーティングし、Web コンテンツをユーザーに返します。 Express はまた、Web アプリケーションによる HTTP Cookie の使用とクエリ文字列の処理を支援します。

Express は Node.js パッケージです。 Node.js に付属する npm ユーティリティを使用し、Node.js パッケージをインストールし、管理します。 このユニットの後半では、package.json という名前のファイルを作成して Express とその他の依存関係を定義してから、npm install コマンドを実行してこれらの依存関係をインストールします。

AngularJS は何のために使用されますか。

Express と同様に MEAN という頭字語に含まれる (A に相当する) AngularJS をまだインストールしていません。

AngularJS を利用することで、Web アプリケーションの記述やテストがより簡単になります。Web ページの ''外観'' (HTML コード) を Web ページの動作から効果的に切り離すことができるためです。 Model-View-Controller (MVC) パターンやデータ バインディングの概念に詳しい場合、AngularJS は見慣れたものに思えるでしょう。

AngularJS はいわゆるフロントエンド JavaScript フレームワークです。つまり、アプリケーションにアクセスするクライアントで利用できれば十分となります。 言い換えると、AngularJS は Web サーバーではなく、Web ブラウザーで実行されます。 また、AngularJS は JavaScript であるため、AngularJS を利用すれば、ページに表示する Web サーバーから簡単にデータを取得できます。

AngularJS は実際にはインストールしません。 その代わりに、他の JavaScript ライブラリの場合と同じように、JavaScript ファイルの参照を HTML ページに追加します。 AngularJS はいくつかの方法で Web ページに追加できます。 ここでは、コンテンツ配信ネットワーク (CDN) から AngularJS を読み込みます。 CDN は画像、動画、その他のコンテンツを地域別に配信することでダウンロード速度を改善する手法です。

CDN から AngularJS をロードするサンプルを示しますが、このコードはまだ追加しないでください。 通常、このコードは HTML ページの <head> セクションに追加します。

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script>

Note

AngularJS と Angular を取り違えないでください。 この 2 つは概念の多くが似ていますが、AngularJS は Angular の前身です。 AngularJS は今でも Web アプリケーションの構築のために一般的に使用されています。 AngularJS は JavaScript を基盤としていますが、Angular の基盤は TypeScript です。これは JavaScript プログラムの記述を簡単にするプログラミング言語です。

アプリケーションはどのような方法でビルドしますか。

ここでは、基本的なプロセスを使用します。 Cloud Shell からアプリケーション コードを記述し、SCP (セキュア コピー プロトコル) を使用してファイルを VM にコピーします。 次に、Node.js アプリケーションを起動し、ブラウザーで結果を確認します。

実際には一般的に、ラップトップやローカルで実行している仮想マシンなど、よりローカルな環境で Web アプリケーションを記述し、テストします。 その後、Git などのバージョン コントロール システムにコードを格納し、Azure DevOps などの CI/CD (継続的インテグレーションと継続的デリバリー) システムを使用して変更をテストし、VM にアップロードできます。 このモジュールの終わりで他のリソースも紹介します。

書籍 Web アプリケーションを作成する

ここで、Web アプリケーションを構成するあらゆるコード、スクリプト、HTML ファイルを作成します。 簡潔にするために、各ファイルの重要な部分を強調しますが、完全な詳細には触れません。

まだ VM に SSH 接続していたら、exit を実行して SSH セッションを終了し、Cloud Shell に戻ります。

exit

Cloud Shell セッションに戻りました。

ファイルを作成する

  1. Cloud Shell から、これらのコマンドを実行し、Web アプリケーションのフォルダーとファイルを作成します。

    cd ~
    mkdir Books
    touch Books/server.js
    touch Books/package.json
    mkdir Books/app
    touch Books/app/model.js
    touch Books/app/routes.js
    mkdir Books/public
    touch Books/public/script.js
    touch Books/public/index.html
    

    次のようなものが含まれます。

    • Books はプロジェクトのルート ディレクトリです。
      • server.js によって Web アプリケーションのエントリ ポイントが定義されます。 必要な Node.js パッケージがロードされ、待機するポートが指定され、入ってくる HTTP トラフィックの待ち受けが開始されます。
      • package.json からアプリケーションに関する情報が提供されます。その名前、説明、アプリケーションで実行する必要がある Node.js パッケージなどです。
    • Books/app サーバーで実行するコードが含まれます。
      • model.js によってデータベース接続とスキーマが定義されます。 アプリケーションのデータ モデルと考えてください。
      • routes.js によって要求のルーティングが処理されます。 たとえば、/book エンドポイントへの GET 要求が定義され、データベースにある全書籍の一覧が提供されます。
    • Books/public クライアントのブラウザーに直接提供されるファイルが含まれます。
      • index.html にはインデックス ページが含まれます。 ユーザーが書籍情報を送信するための Web フォームが含まれています。 また、データベースの全書籍が表示されます。データベースからエントリを削除できます。
      • script.js には、ユーザーのブラウザーで実行される JavaScript コードが含まれています。 書籍を一覧表示する要求、書籍をデータベースに追加する要求、データベースから書籍を削除する要求をサーバーに送信できます。
  2. code コマンドを実行し、Cloud Shell エディターからファイルを開きます。

    code Books
    

データ モデルを作成する

  1. エディターから app/model.js を開き、以下を追加します。

    var mongoose = require('mongoose');
    var dbHost = 'mongodb://localhost:27017/Books';
    mongoose.connect(dbHost, { useNewUrlParser: true } );
    mongoose.connection;
    mongoose.set('debug', true);
    var bookSchema = mongoose.Schema( {
        name: String,
        isbn: {type: String, index: true},
        author: String,
        pages: Number
    });
    var Book = mongoose.model('Book', bookSchema);
    module.exports = Book;
    

    重要

    エディターでファイルにコードを貼り付けたり、コードを変更したりした場合は、その後に必ず [...] メニューまたはアクセス キー (Windows と Linux では Ctrl + S、macOS では Command+S) を使用して保存してください。

    このコードでは MongoDB とのデータ送信プロセスを単純にする目的で Mongoose が使用されます。 Mongoose はデータをモデル化するためのスキーマベースのシステムです。 与えられたスキーマにより "Book" という名前のデータベース ドキュメントがコードで定義されます。 このスキーマによって、1 つの書籍を表す 4 つのフィールドが定義されます。

    • 書籍の名前 (タイトル)
    • 書籍を一意に識別する国際標準図書番号 (ISBN)
    • 作者
    • 含まれるページ数

    次に、GET、POST、DELETE 要求をデータベース操作にマッピングする HTTP ハンドラーを作成します。

HTTP 要求を処理する Express.js ルートを作成する

  1. エディターから app/routes.js を開き、次のコードを追加します。

    var path = require('path');
    var Book = require('./model');
    var routes = function(app) {
        app.get('/book', function(req, res) {
            Book.find({}, function(err, result) {
                if ( err ) throw err;
                res.json(result);
            });
        });
        app.post('/book', function(req, res) {
            var book = new Book( {
                name:req.body.name,
                isbn:req.body.isbn,
                author:req.body.author,
                pages:req.body.pages
            });
            book.save(function(err, result) {
                if ( err ) throw err;
                res.json( {
                    message:"Successfully added book",
                    book:result
                });
            });
        });
        app.delete("/book/:isbn", function(req, res) {
            Book.findOneAndRemove(req.query, function(err, result) {
                if ( err ) throw err;
                res.json( {
                    message: "Successfully deleted the book",
                    book: result
                });
            });
        });
        app.get('*', function(req, res) {
            res.sendFile(path.join(__dirname + '/public', 'index.html'));
        });
    };
    module.exports = routes;
    

    このコードによってアプリケーションの 4 つのルートが作成されます。 それぞれの概要は次のようになります。

    HTTP 動詞 エンドポイント 説明
    GET /book データベースから全書籍を取得します。
    POST /book Web フォームでユーザーが提供したフィールドに基づいて Book オブジェクトを作成し、そのオブジェクトをデータベースに書き込みます。
    DELETE /book/:isbn その ISBN によって識別された書籍をデータベースから削除します。
    GET * その他のルートが一致しないとき、インデックス ページを返します。

    Express.js では、ルート処理コードで HTTP 応答を直接提供できます。あるいは、ファイルから静的コンテンツを提供できます。 このコードでは両方が表示されます。 最初の 3 つのルートからは、書籍 API 要求の JSON データが返されます。 4 つ目のルート (既定のケース) からはインデックス ファイル index.html の内容が返されます。

クライアント側 JavaScript アプリケーションを作成する

  1. エディターから public/script.js を開き、次のコードを追加します。

    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope, $http) {
        var getData = function() {
            return $http( {
                method: 'GET',
                url: '/book'
            }).then(function successCallback(response) {
                $scope.books = response.data;
            }, function errorCallback(response) {
                console.log('Error: ' + response);
            });
        };
        getData();
        $scope.del_book = function(book) {
            $http( {
                method: 'DELETE',
                url: '/book/:isbn',
                params: {'isbn': book.isbn}
            }).then(function successCallback(response) {
                console.log(response);
                return getData();
            }, function errorCallback(response) {
                console.log('Error: ' + response);
            });
        };
        $scope.add_book = function() {
            var body = '{ "name": "' + $scope.Name +
            '", "isbn": "' + $scope.Isbn +
            '", "author": "' + $scope.Author +
            '", "pages": "' + $scope.Pages + '" }';
            $http({
                method: 'POST',
                url: '/book',
                data: body
            }).then(function successCallback(response) {
                console.log(response);
                return getData();
            }, function errorCallback(response) {
                console.log('Error: ' + response);
            });
        };
    });
    

    このコードによって "myApp" という名前のモジュールと "myCtrl" という名前のコントローラーが定義されるしくみに注目してください。 モジュールとコントローラーの動作についてはここでは詳しく触れませんが、次の手順ではこれらの名前を使用し、ユーザー インターフェイス (HTML コード) とアプリケーションのビジネス ロジックをバインドします。

    サーバーでさまざまな GET、POST、DELETE 操作を処理する 4 つのルートを先に作成しました。 このコードはそれらの操作に類似していますが、クライアント側 (ユーザーの Web ブラウザー) からとなります。

    たとえば、getData 関数によって /book エンドポイントに GET 要求が送信されます。 この要求はサーバーによって処理されることを思い出してください。データベースから全書籍情報を取得し、その情報を JSON データとして返します。 結果的に生成される JSON データが $scope.books 変数に割り当てられるしくみに注目してください。 これが Web ページでユーザーに表示される内容に与える影響を次の手順で学習します。

    このコードによって、ページの読み込み時、getData 関数が呼び出されます。 del_book 関数と add_book 関数を調べるとその動作を理解できます。 既定のハンドラーによって JSON データではなくインデックス ページが返されるため、サーバーの既定のハンドラーと一致させるクライアント側コードは必要ありません。

ユーザー インターフェイスを作成する

  1. エディターから public/index.html を開き、次のコードを追加します。

    <!doctype html>
    <html ng-app="myApp" ng-controller="myCtrl">
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script>
        <script src="script.js"></script>
    </head>
    <body>
        <div>
        <table>
            <tr>
            <td>Name:</td>
            <td><input type="text" ng-model="Name"></td>
            </tr>
            <tr>
            <td>Isbn:</td>
            <td><input type="text" ng-model="Isbn"></td>
            </tr>
            <tr>
            <td>Author:</td>
            <td><input type="text" ng-model="Author"></td>
            </tr>
            <tr>
            <td>Pages:</td>
            <td><input type="number" ng-model="Pages"></td>
            </tr>
        </table>
        <button ng-click="add_book()">Add</button>
        </div>
        <hr>
        <div>
        <table>
            <tr>
            <th>Name</th>
            <th>Isbn</th>
            <th>Author</th>
            <th>Pages</th>
            </tr>
            <tr ng-repeat="book in books">
            <td><input type="button" value="Delete" data-ng-click="del_book(book)"></td>
            <td>{{book.name}}</td>
            <td>{{book.isbn}}</td>
            <td>{{book.author}}</td>
            <td>{{book.pages}}</td>
            </tr>
        </table>
        </div>
    </body>
    </html>
    

    このコードにより、書籍データを送信するための 4 つのフィールドと、データベースに格納されているすべての書籍を表示するテーブルを含む、基本的な HTML フォームが作成されます。

    これは標準的な HTML コードですが、ng- HTML 属性には見慣れないものがあるかもしれません。 これらの HTML 属性により、AngularJS コードがユーザー インターフェイスに接続されます。 たとえば、[追加] をクリックすると、AngularJS によって add_book 関数が呼び出され、フォーム データがサーバーに送信されます。

    ここでコードを調べると、ng- 属性のそれぞれがアプリケーションのビジネス ロジックとどのように関連しているのか理解できます。

アプリケーションをホストするための Express.js サーバーを作成する

  1. エディターから server.js を開き、次のコードを追加します。

    var express = require('express');
    var bodyParser = require('body-parser');
    var app = express();
    app.use(express.static(__dirname + '/public'));
    app.use(bodyParser.json());
    require('./app/routes')(app);
    app.set('port', 80);
    app.listen(app.get('port'), function() {
        console.log('Server up: http://localhost:' + app.get('port'));
    });
    

    このコードでは、Web アプリケーション自体が作成されます。 public ディレクトリから静的ファイルが提供され、前に定義されたルートを利用し、要求が処理されます。

パッケージ情報と依存関係を定義する

package.json からは、その名前、説明、アプリケーションで実行する必要がある Node.js パッケージなど、アプリケーションに関する情報が提供されることを思い出してください。

  1. エディターから package.json を開き、次のコードを追加します。

    {
      "name": "books",
      "description": "Sample web app that manages book information.",
      "license": "MIT",
      "repository": {
        "type": "git",
        "url": "https://github.com/MicrosoftDocs/mslearn-build-a-web-app-with-mean-on-a-linux-vm"
      },
      "main": "server.js",
      "dependencies": {
        "express": "~4.16",
        "mongoose": "~5.3",
        "body-parser": "~1.18"
      }
    }
    

名前、説明、ライセンスなど、アプリケーションに関する情報 (メタデータ) が表示されます。

repository フィールドによってコードの保管場所が指定されます。 参照が必要であれば、後で GitHub にあるコードを確認できます。URL はここに示すものになります。

main フィールドによって、アプリケーションのエントリ ポイントが定義されます。 ここでは完全を期すために提供されますが、重要ではありません。他者がダウンロードして使用するために Node.js パッケージとしてアプリケーションを公開する予定がないからです。

dependencies フィールドは重要です。 アプリケーションで必要とされる Node.js パッケージが定義されます。 これからすぐに VM に 2 回目の接続を行います。npm install コマンドを実行し、これらのパッケージをインストールします。

Node パッケージでは通常、セマンティック バージョニング バージョン管理スキームが使用されます。 バージョン番号には、3 つのコンポーネントが含まれています。メジャー バージョン、マイナー バージョン、パッチです。 ここでのチルダ ~ 表記は、与えられたメジャー バージョンとマイナー バージョンの下で最新のパッチ バージョンをインストールするように npm に通知するものです。 ここに表示されるバージョンは、このモジュールがテストされた最新版です。 実際には、アプリケーションを更新したり、テストしたりする過程でバージョンをインクリメントし、それぞれの依存パッケージにより提供される最新機能を使用できます。

VM にファイルをコピーする

先に進む前に、VM の IP アドレスが手元にあることを確認してください。 アドレスがない場合、Cloud Shell からこれらのコマンドを実行して取得します。

ipaddress=$(az vm show \
  --name MeanStack \
  --resource-group "<rgn>[sandbox resource group name]</rgn>" \
  --show-details \
  --query [publicIps] \
  --output tsv)
echo $ipaddress
  1. ファイルの編集はこれで完了です。 変更が各ファイルに保存されていることを確認し、エディターを閉じます。

    エディターを閉じるには、右上隅にある省略記号を選択し、[エディターを閉じる] を選びます。

  2. 次の scp コマンドを実行し、Cloud Shell セッションの ~/Books ディレクトリの内容を VM 上の同じディレクトリ名にコピーします。

    scp -r ~/Books azureuser@$ipaddress:~/Books
    

追加 Node パッケージをインストールする

開発プロセス中、使用するべき追加 Node パッケージが見つかったとします。 たとえば、app/model.js はこのラインから始まることを思い出してください。

var mongoose = require('mongoose');

このアプリケーションでは、MongoDB との間のデータ転送を支援する目的で Mongoose が使用されます。

また、Express.js と body-parser パッケージも必要です。 body-parser は、クライアントによって送信された Web フォームからのデータを Express で操作するためのプラグインです。

それでは VM に接続し、package.json に指定したパッケージをインストールしましょう。

  1. VM に接続する前に、VM の IP アドレスが手元にあることを確認してください。 アドレスがない場合は、前のセクションの Cloud Shell コマンドを実行して取得します。

  2. 先ほど行ったように、VM への SSH 接続を作成します。

    ssh azureuser@$ipaddress
    
  3. ホーム ディレクトリの下にある Books ディレクトリに移動します。

    cd ~/Books
    
  4. npm install を実行し、依存パッケージをインストールします。

    sudo apt install npm -y && npm install
    

次のセクションのために SSH 接続は開いたままにします。

アプリケーションをテストする

これで Node.js Web アプリケーションをテストする準備ができました。

  1. ~/Books ディレクトリから、このコマンドを実行して Web アプリケーションを起動します。

    sudo nodejs server.js
    

    このコマンドによってアプリケーションが起動します。入ってくる HTTP 要求をポート 80 で待ち受けます。

  2. 別のブラウザー タブから、VM のパブリック IP アドレスに移動します。

    Web フォームを含むインデックス ページが表示されます。

    Screenshot of the book web page with a form and submission button.

    データベースに書籍をいくつか追加してみてください。 書籍を追加するたびに、書籍の一覧のページが更新されます。

    Screenshot of the book web page with sample data populated.

    [削除] を選択して、データベースから書籍を削除することもできます。