練習 - 建立基本的 Web 應用程式
至此您已在 Ubuntu 虛擬機器 (VM) 上安裝了 MongoDB 及 Node.js。 現在您可以建立基本 Web 應用程式,以觀察其運作方式。 過程中,您看到 AngularJS 和 Express 如何能搭配這一切運作。
而其中一種絕佳的學習方式,便是透過範例來了解作法。 您建置的 Web 應用程式會實作基本的書籍資料庫。 該 Web 應用程式可讓您列出書籍的相關資訊、加入新書籍,以及刪除現有的書籍。
您在這裡看見的 Web 應用程式能示範許多適用於大部分 MEAN 堆疊 Web 應用程式的概念。 視您的需求和興趣而定,您可以探索建置屬於自己的 MEAN 堆疊應用程式所需的功能。
這是 Books Web 應用程式完成時的模樣。
讓我們看看 MEAN 堆疊的每個元件如何在網頁中發揮功用。
- MongoDB 會儲存書籍的相關資訊。
- Express.js 會將每個 HTTP 要求路由到適當的處理常式。
- AngularJS 會將使用者介面與程式的商務邏輯連結。
- Node.js 會裝載伺服器端的應用程式。
重要
基於學習目的,在這裡您將會建置基本 Web 應用程式。 其目的是為了測試您的 MEAN 堆疊,並讓您初步了解其運作方式。 此應用程式的安全性和準備性均不足以作為生產用途。
那麼 Express 呢?
到目前為止,您已在 VM 上安裝 MongoDB 和 Node.js。 那麼 MEAN 首字母縮略字中的 E 所代表的 Express.js 呢?
Express.js 是專為 Node.js 建置的網頁伺服器架構,能簡化建置 Web 應用程式的程序。
Express 的主要目的是處理要求路由。 「路由」指的是應用程式回應針對特定端點之要求的方式。 端點是由路徑 (或 URI) 及要求方法 (例如 GET 或 POST) 所組成。 例如,在回應針對 /book
端點的 GET 要求時,您可能會提供資料庫中所有書籍的清單。 而對於針對 /book
端點的 POST 要求,您可能會根據使用者輸入至 Web 表單的欄位,將項目加入到資料庫中。
在您即將建置的 Web 應用程式中,您會使用 Express 來路由 HTTP 要求,並將網頁內容傳回給您的使用者。 Express 也可以協助 Web 應用程式搭配 HTTP Cookie 運作,並處理查詢字串。
Express 是 Node.js 套件。 您會使用 npm 公用程式 (隨附於 Node.js) 來安裝及管理 Node.js 套件。 稍後在這個單元中,您會建立名為 package.json
的檔案來定義 Express 及其他相依性,然後執行 npm install
命令來安裝這些相依性。
那麼 AngularJS 呢?
和 Express 一樣,MEAN 首字母縮略字中的 A 所代表的 AngularJS 也尚未安裝。
AngularJS 能使撰寫及測試 Web 應用程式的程序變得較為簡單,因為它能讓您更清楚地將網頁的外觀 (HTML 程式碼) 與網頁的運作方式區分開來。 如果您熟悉 Model–View–Controller (MVC) 模式,或是資料繫結的概念,那麼您應該會對 AngularJS 感到很熟悉。
AngularJS 是所謂的「前端」JavaScript 架構,這代表只需要在存取應用程式的用戶端上提供它即可。 換句話說,AngularJS 會在使用者的網頁瀏覽器上執行,而非在您的網頁伺服器上執行。 且由於 AngularJS 是 JavaScript,您可以用它來輕鬆地從網頁伺服器上擷取資料以顯示在頁面上。
您不會真的安裝 AngularJS。 相反地,和處理其他 JavaScript 程式庫相同,您會將參考新增至 HTML 頁面中的 JavaScript 檔案上。 有數種方式可以將 AngularJS 包含到網頁中。 在這裡,您將會從內容傳遞網路 (CDN) 載入 AngularJS。 CDN 是一種依地理位置散佈影像、視訊及其他內容以提升下載速度的方式。
還不要新增此程式碼,但這裡有一個從 CDN 載入 AngularJS 的範例。 您通常會將此程式碼新增至 HTML 頁面的 <head>
區段。
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script>
注意
請不要將 AngularJS 與 Angular 混淆了。 雖然這兩者有許多概念都是相同的,但 AngularJS 是 Angular 的前身。 AngularJS 仍然常被用來建置 Web 應用程式。 雖然 AngularJS 是以 JavaScript 為基礎,Angular 則是以 TypeScript 為基礎,這是一種能簡化撰寫 JavaScript 程式的程式設計語言。
要如何建置應用程式?
您會在此使用基本的流程。 從 Cloud Shell 撰寫應用程式程式碼,然後使用安全複製通訊協定 (SCP) 來將檔案複製到 VM。 然後,您會啟動 Node.js 應用程式,並在瀏覽器中查看結果。
在實務上,您通常會在更加本機的環境中撰寫及測試 Web 應用程式,例如從您的膝上型電腦,或是從本機執行的虛擬機器。 您可以將程式碼儲存在 Git 之類的版本控制系統中。 然後,使用 Azure DevOps 之類的持續整合與持續傳遞 (CI/CD) 系統來測試您的變更,並將其上傳至您的 VM。 我們會在此課程模組結束時提供更多相關資源。
建立 Books Web 應用程式
在這裡,您會建立組成 Web 應用程式的所有程式碼、指令碼及 HTML 檔案。 為求簡單明了,我們會重點描述每個檔案的重要部分,但不會詳述完整的詳細資料。
如果您仍然透過 SSH 連線至 VM,請執行 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) 來儲存該檔案。
此程式碼會使用 Mongoose 來簡化針對 MongoDB 來回傳送檔案的程序。 Mongoose 是以結構描述為基礎,用來處理模型化資料的系統。 程式碼會搭配所提供的結構描述,定義稱為 "Book" 的資料庫文件。 該結構描述會定義描述單一書籍的四個欄位:
- 書籍的名稱 (或標題)
- 能唯一識別該書籍的國際標準書號 (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;
此程式碼會針對應用程式建立四個路由。 以下是每個路由的簡短概觀。
HTTP 指令動詞 端點 描述 GET /book
能從資料庫擷取所有書籍。 POST /book
根據使用者在 Web 表單上所提供的欄位建立 Book
物件,然後將該物件寫入至資料庫。DELETE /book/:isbn
以書籍的 ISBN 來識別,並將該書籍從資料庫中刪除。 GET *
在沒有其他相符路由的情況下,傳回索引頁面。 Express.js 可以直接在處理路由的程式碼中提供 HTTP 回應,或是從檔案提供靜態內容。 此程式碼會同時顯示這兩者。 前三個路由會針對書籍 API 要求傳回 JSON 資料。 第四個路由 (預設情況) 會傳回索引檔案的內容,
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 作業的四個路由。 此程式碼類似於那些相同的作業,但會從用戶端 (使用者的網頁瀏覽器) 執行。
例如,
getData
函式會傳送 GET 要求至/book
端點。 您應該還記得,伺服器處理此要求的方法是,透過從資料庫擷取所有書籍的相關資訊,並在回應中以 JSON 資料的形式傳回該資訊。 請留意回應中的 JSON 資料是以何種方式指派給$scope.books
變數。 在下個步驟中,您能了解這個程式碼會如何影響使用者在網頁上所能看見的內容。此程式碼會在頁面載入時呼叫
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>
此程式碼會建立具有四個欄位的基本 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
欄位會指定程式碼的維護位置。 您稍後可以透過在此顯示的 URL,檢閱位於 GitHub 上的程式碼,以作為參考。
main
欄位會定義應用程式的進入點。 為求完整,我們在此提供這個欄位。 不過,只有在您打算將應用程式以 Node.js 套件的形式發佈給其他人下載及使用時,進入點才有重要性。
dependencies
欄位很重要。 它會定義應用程式所需的 Node.js 套件。 您很快就會第二次連線至您的 VM,並執行 npm install
命令以安裝這些套件。
Node 套件通常會使用語意化版本控制系統版本控制配置。 版本號碼包含三個元件:主要版本、次要版本,以及修補程式。 這裡的波狀符號 ~
標記法會要求 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');
您應該還記得應用程式會使用 Mongoose 來協助針對 MongoDB 資料庫來回傳送檔案。
應用程式也需要 Express.js 和 body-parser 套件。 body-parser 外掛程式讓 Express 能使用由用戶端傳送之 Web 表單中的資料。
讓我們連線至您的 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
此命令會透過在連接埠 80 上接聽傳入 HTTP 要求來啟動應用程式。
從個別的瀏覽器索引標籤,瀏覽到 VM 的公用 IP 位址。
您會看到索引頁面,其中包含了 Web 表單。
嘗試將幾本書籍加入資料庫。 每當您加入書籍時,頁面便會更新以顯示完整的書籍清單。
您也可以選取 [刪除],從資料庫將書籍刪除。