演習 - 基本的な Web アプリケーションの作成
ここまでで、MongoDB と Node.js を Ubuntu 仮想マシン (VM) にインストールしました。 次に基本的な Web アプリケーションを作成し、動作を確認します。 その過程で、AngularJS と Express をどのように組み込むかがわかります。
例を利用することは優れた学習方法です。 構築する Web アプリケーションは、基本的な書籍データベースを実装します。 この Web アプリケーションによって、書籍情報を一覧表示したり、新しい書籍を追加したり、既存の書籍を削除したりすることができます。
ここで見る Web アプリケーションでは、ほとんどの MEAN スタック Web アプリケーションに適用される多くの概念を見ることができます。 ニーズや関心事に基づき、必要な機能をいろいろ試し、独自の MEAN スタック アプリケーションを構築できます。
Books Web アプリケーションの外観は次のようになります。
MEAN スタックの各コンポーネントは次のように動作します。
- MongoDB によって書籍情報が格納されます。
- Express.js によって各 HTTP 要求が該当するハンドラーに送信されます。
- AngularJS によってユーザー インターフェイスとプログラムのビジネス ロジックが接続されます。
- Node.js によってサーバー側アプリケーションがホストされます。
重要
学習目的のため、ここでは基本的な Web アプリケーションを構築します。 その目的は MEAN スタックをテストし、そのしくみを理解することにあります。 このアプリケーションは安全性が不十分であり、運用環境で使用する準備ができていません。
Express とは何ですか。
ここまでで、MongoDB と Node.js を VM にインストールしました。 MEAN という頭文字の E に相当する Express.js は何のために使用されますか。
Express.js は、Web アプリケーションの構築プロセスを簡略化する、Node.js 用に構築された Web サーバー フレームワークです。
Express の主な目的は、要求のルーティングを処理することにあります。 ルーティングとは、特定のエンドポイントへの要求にアプリケーションが応答するしくみです。 エンドポイントはパス、URI、要求メソッド (GET や POST など) から構成されます。 たとえば、/book
エンドポイントへの GET 要求に応答するためにデータベースにある全書籍の一覧を提供することがあります。 ユーザーが Web フォームに入力したフィールドに基づいてデータベースにエントリを追加し、/book
エンドポイントへの POST 要求に応答することもあります。
この後すぐに作成する Web アプリケーションでは、Express を使用して HTTP 要求をルーティングし、Web コンテンツをユーザーに返します。 Express はまた、Web アプリケーションによる HTTP Cookie の使用とクエリ文字列の処理を支援します。
Express は Node.js パッケージです。 Node.js に付属する npm ユーティリティを使用し、Node.js パッケージをインストールし、管理します。 このユニットの後半では、package.json
という名前のファイルを作成して Express とその他の依存関係を定義してから、npm install
コマンドを実行してこれらの依存関係をインストールします。
AngularJS は何のために使用されますか。
Express と同様に、AngularJS (MEAN での頭字語の A) はまだインストールされていません。
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 セッションに戻りました。
ファイルを作成する
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
Web アプリケーションには、次のフォルダーとファイルが含まれています。
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 コードが含まれています。 書籍を一覧表示する要求、書籍をデータベースに追加する要求、データベースから書籍を削除する要求をサーバーに送信できます。
code
コマンドを実行し、Cloud Shell エディターからファイルを開きます。code Books
データ モデルを作成する
エディターから
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 ルートを作成する
エディターから
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 アプリケーションを作成する
エディターから
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 データではなくインデックス ページが返されるため、サーバーの既定のハンドラーと一致させるクライアント側コードは必要ありません。
ユーザー インターフェイスを作成する
エディターから
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 サーバーを作成する
エディターから
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 パッケージなど、アプリケーションに関する情報が提供されることを思い出してください。
エディターから
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 に接続し、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
ファイルの編集はこれで完了です。 変更が各ファイルに保存されていることを確認し、エディターを閉じます。
エディターを閉じるには、右上隅にある省略記号を選択し、[エディターを閉じる] を選びます。
次の
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
に指定したパッケージをインストールしましょう。
VM に接続する前に、VM の IP アドレスが手元にあることを確認してください。 アドレスがない場合は、前のセクションの Cloud Shell コマンドを実行して取得します。
先ほど行ったように、VM への SSH 接続を作成します。
ssh azureuser@$ipaddress
ホーム ディレクトリの下にある
Books
ディレクトリに移動します。cd ~/Books
npm install
を実行し、依存パッケージをインストールします。sudo apt install npm -y && npm install
次のセクションのために SSH 接続は開いたままにします。
アプリケーションをテストする
これで Node.js Web アプリケーションをテストする準備ができました。
~/Books
ディレクトリから、このコマンドを実行して Web アプリケーションを起動します。sudo nodejs server.js
このコマンドによってアプリケーションが起動します。入ってくる HTTP 要求をポート 80 で待ち受けます。
別のブラウザー タブから、VM のパブリック IP アドレスに移動します。
Web フォームを含むインデックス ページが表示されます。
データベースに書籍をいくつか追加してみてください。 書籍を追加するたびに、書籍の一覧のページが更新されます。
[削除] を選択して、データベースから書籍を削除することもできます。