練習 - 建立基本的 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 工作階段。

建立檔案

  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
    

    該 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 程式碼。 它可以傳送要求至伺服器,以列出書籍、將書籍加入資料庫,以及從資料庫移除書籍。
  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) 來儲存該檔案。

    此程式碼會使用 Mongoose 來簡化針對 MongoDB 來回傳送檔案的程序。 Mongoose 是以結構描述為基礎,用來處理模型化資料的系統。 程式碼會搭配所提供的結構描述,定義稱為 "Book" 的資料庫文件。 該結構描述會定義描述單一書籍的四個欄位:

    • 書籍的名稱 (或標題)
    • 能唯一識別該書籍的國際標準書號 (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;
    

    此程式碼會針對應用程式建立四個路由。 以下是每個路由的簡短概觀。

    HTTP 指令動詞 端點 描述
    GET /book 能從資料庫擷取所有書籍。
    POST /book 根據使用者在 Web 表單上所提供的欄位建立 Book 物件,然後將該物件寫入至資料庫。
    DELETE /book/:isbn 以書籍的 ISBN 來識別,並將該書籍從資料庫中刪除。
    GET * 在沒有其他相符路由的情況下,傳回索引頁面。

    Express.js 可以直接在處理路由的程式碼中提供 HTTP 回應,或是從檔案提供靜態內容。 此程式碼會同時顯示這兩者。 前三個路由會針對書籍 API 要求傳回 JSON 資料。 第四個路由 (預設情況) 會傳回索引檔案的內容,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 作業的四個路由。 此程式碼類似於那些相同的作業,但會從用戶端 (使用者的網頁瀏覽器) 執行。

    例如,getData 函式會傳送 GET 要求至 /book 端點。 您應該還記得,伺服器處理此要求的方法是,透過從資料庫擷取所有書籍的相關資訊,並在回應中以 JSON 資料的形式傳回該資訊。 請留意回應中的 JSON 資料是以何種方式指派給 $scope.books 變數。 在下個步驟中,您能了解這個程式碼會如何影響使用者在網頁上所能看見的內容。

    此程式碼會在頁面載入時呼叫 getData 函式。 您可以檢查 del_bookadd_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>
    

    此程式碼會建立具有四個欄位的基本 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 欄位會指定程式碼的維護位置。 您稍後可以透過在此顯示的 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
  1. 您已經完成檔案的編輯。 請確定您已儲存對每個檔案所做的變更,然後關閉編輯器。

    若要關閉編輯器,請選取位於右上角的省略符號,然後選取 [關閉編輯器]

  2. 執行下列 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中所指定的套件。

  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
    

    此命令會透過在連接埠 80 上接聽傳入 HTTP 要求來啟動應用程式。

  2. 從個別的瀏覽器索引標籤,瀏覽到 VM 的公用 IP 位址。

    您會看到索引頁面,其中包含了 Web 表單。

    書籍網頁的螢幕擷取畫面,顯示表單與提交按鈕。

    嘗試將幾本書籍加入資料庫。 每當您加入書籍時,頁面便會更新以顯示完整的書籍清單。

    書籍網頁的螢幕擷取畫面,顯示填入的範例資料。

    您也可以選取 [刪除],從資料庫將書籍刪除。