練習 - 建立基本的 Web 應用程式

已完成 100 XP

此單元需要沙箱才能完成。 沙箱可讓您存取免費資源。 您的個人訂用帳戶不須付費。 這些沙箱僅可用於完成 Microsoft Learn 上的訓練。 禁止用於其他任何用途,否則可能導致永久無法存取沙箱。

Microsoft 提供了教育用途的實驗室體驗和相關內容。 所有呈現內容都為 Microsoft 所有,並僅供學習此 Microsoft Learn 課程模組所涵蓋的產品及服務所用。

至此您已在 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> 區段。

HTML
<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。

Bash
exit

您現在已返回 Cloud Shell 工作階段。

建立檔案

  1. 從 Cloud Shell 執行這些命令,以建立適用於您 Web 應用程式的資料夾及檔案:

    Bash
    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 編輯器開啟檔案。

    Bash
    code Books
    

建立資料模型

  1. 從編輯器開啟 app/model.js 並新增下列程式碼:

    JavaScript
    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 並新增下列程式碼:

    JavaScript
    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 並新增此程式碼:

    JavaScript
    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 並新增此程式碼:

    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 並新增此程式碼:

    JavaScript
    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 並新增此程式碼:

    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 執行以下命令來擷取:

Azure CLI
ipaddress=$(az vm show \
  --name MeanStack \
  --resource-group "[sandbox resource group name]" \
  --show-details \
  --query [publicIps] \
  --output tsv)
Bash
echo $ipaddress
  1. 您已經完成檔案的編輯。 請確定您已儲存對每個檔案所做的變更,然後關閉編輯器。

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

  2. 執行下列 scp 命令,以將 Cloud Shell 工作階段中 ~/Books 目錄的內容複製到 VM 上相同名稱的目錄中:

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

安裝更多 Node 套件

讓我們假設您在開發程序期間發現了更多想要使用的 Node 套件。 例如,您應該還記得 app/model.js 是以此行作為開頭。

JavaScript
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 連線:

    Bash
    ssh azureuser@$ipaddress
    
  3. 移至主目錄之下的 Books 目錄:

    Bash
    cd ~/Books
    
  4. 執行 npm install 以安裝相依套件:

    Bash
    sudo apt install npm -y && npm install
    

保留 SSH 連線,供下一個區段使用。

測試應用程式

您現在已經準備好測試 Node.js Web 應用程式了!

  1. ~/Books 目錄執行此命令來啟動 Web 應用程式:

    Bash
    sudo nodejs server.js
    

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

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

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

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

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

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

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


下一個單元: 摘要

上一個 下一個