

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 將 Node.js Express 應用程式部署至 Elastic Beanstalk
<a name="create_deploy_nodejs_express"></a>

本節將逐步引導您使用 Elastic Beanstalk 命令列界面 (EB CLI) 將範例應用程式部署至 Elastic Beanstalk，然後更新應用程式以使用 [Express](http://expressjs.com/) 架構。

## 先決條件
<a name="create_deploy_nodejs_express.prerequisites"></a>

本教學課程需要下列先決條件：
+ Node.js 執行階段
+ 預設的 Node.js 套件管理工具軟體 npm
+ Express 命令列產生器
+ Elastic Beanstalk 命令列界面 (EB CLI)

有關安裝所列出之前三個元件和設定本機開發環境的詳細資訊，請參閱 [設定 Elastic Beanstalk 的 Node.js 開發環境](nodejs-devenv.md)。在本教學課程中，您不需要安裝適用於 Node.js 的 AWS SDK，這也在參考主題中提及。

有關安裝和設定 EB CLI 的詳細資訊，請參閱 [使用設定指令碼安裝 EB CLI （建議）](eb-cli3.md#eb-cli3-install) 和 [設定 EB CLI](eb-cli3-configuration.md)。

## 建立 Elastic Beanstalk 環境
<a name="create_deploy_nodejs_express.eb_init-rds"></a>

**應用程式目錄**  
對於應用程式原始碼套件，本教學課程使用的是名為 `nodejs-example-express-rds` 的目錄。為本教學課程建立 `nodejs-example-express-rds` 目錄。

```
~$ mkdir nodejs-example-express-rds
```

**注意**  
本章中的每個教學課程皆會使用其自身的應用程式原始碼套件目錄。目錄名稱與教學課程所使用的範例應用程式名稱相符。

將您目前的工作目錄變更為 `nodejs-example-express-rds`。

```
~$ cd nodejs-example-express-rds
```

現在，來設定執行 Node.js 平台和範例應用程式的 Elastic Beanstalk 環境。我們將會使用 Elastic Beanstalk 命令列介面 (EB CLI)。

**設定應用程式的 EB CLI 儲存庫，並建立執行 Node.js 平台的 Elastic Beanstalk 環境**

1. 使用 **[**eb init**](eb3-init.md)** 命令建立一個儲存庫。

   ```
   ~/nodejs-example-express-rds$ eb init --platform node.js --region <region>
   ```

   此命令會在名為 `.elasticbeanstalk` 的資料夾內建立組態檔案，其中會指定應用程式使用的環境設定，並以目前資料夾為名建立 Elastic Beanstalk 應用程式。

1. 使用 **[**eb create**](eb3-create.md)** 命令建立執行範例應用程式的環境。

   ```
   ~/nodejs-example-express-rds$ eb create --sample nodejs-example-express-rds
   ```

   本命令會使用 Node.js 平台的預設設定和下列資源，建立負載平衡的環境：
   + **EC2 執行個體** ‒ Amazon Elastic Compute Cloud (Amazon EC2) 虛擬機器，已設為在您選擇的平台上執行 Web 應用程式。

     每個平台會執行特定的一套軟體、設定檔和指令碼，來支援特定的語言版本、架構、Web 容器或其組合。大多數的平台使用會 Apache 或 NGINX 做為反向代理，此反向代理會在您 Web 應用程式的前景執行、轉傳遞交給此 Web 應用程式的請求、提供靜態資產，並產生存取和錯誤日誌。
   + **執行個體安全群組** - Amazon EC2 安全群組，已設為允許從連接埠 80 傳入的流量。此資源可讓負載平衡器傳來的 HTTP 傳輸資料，到達執行您 Web 應用程式的 EC2 執行個體。在預設情況下，不允許傳輸資料從其他通訊埠傳送。
   + **負載平衡器** - Elastic Load Balancing 負載平衡器，可設定將請求分配到執行您應用程式的執行個體。負載平衡器也讓您的執行個體不需直接連接到網際網路。
   + **負載平衡器安全群組** - Amazon EC2 安全群組，設為允許從連接埠 80 傳入的流量。此資源可讓來自網際網路的 HTTP 傳輸資料到達負載平衡器。在預設情況下，不允許傳輸資料從其他通訊埠傳送。
   + **Auto Scaling 群組** - Auto Scaling 群組，設為在執行個體終止或無法使用時，取代該執行個體。
   + **Amazon S3 儲存貯體** - 儲存位置，用來儲存當您使用 Elastic Beanstalk 時所建立的原始程式碼、日誌和其他成品。
   + **Amazon CloudWatch 警示** - 兩種 CloudWatch 警示，用來監控您環境中執行個體上的負載，會在負載過高或過低時觸發。當警示觸發時，您的 Auto Scaling 群組會擴展或縮減以進行回應。
   + **CloudFormation 堆疊** – Elastic Beanstalk 使用 CloudFormation 啟動環境中的資源並傳播組態變更。資源定義於範本中，您可在 [CloudFormation 主控台](https://console.aws.amazon.com/cloudformation)中檢視此範本。
   + **網域名稱** – 會路由到您 Web 應用程式的網域名稱，其格式為 **subdomain*.*region*.elasticbeanstalk.com*。
**網域安全**  
為了增強 Elastic Beanstalk 應用程式的安全性，我們會在[公共后缀列表 (PSL)](https://publicsuffix.org/) 中註冊網域 *elasticbeanstalk.com*。  
如果您需要在 Elastic Beanstalk 應用程式的預設網域名稱中設定敏感 Cookie，建議您使用字`__Host-`首為 的 Cookie 以提高安全性。此實務可保護您的網域免於跨網站請求偽造嘗試 (CSRF)。如需更多資訊，請參閱 Mozilla 開發人員網路中的[設定 Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes) 頁面。

1. 環境建立完成後，請使用 [**eb open**](eb3-open.md) 命令，在預設瀏覽器中開啟環境 URL。

   ```
   ~/nodejs-example-express-rds$ eb open
   ```

您現在已使用範例應用程式建立 Node.js Elastic Beanstalk 環境。您可以使用自己的應用程式對其進行更新。接下來，我們會更新範例應用程式，以使用 Express 架構。

## 更新應用程式以使用 Express
<a name="create_deploy_nodejs_express.update"></a>

使用範例應用程式建立環境後，您可使用自己的應用程式對其進行更新。在此程序中，我們先執行 **express** 和 **npm install** 命令，以便在應用程式目錄中設定 Express 架構。然後，我們將使用 EB CLI，以便使用已更新的應用程式來更新您的 Elastic Beanstalk 環境。

**欲更新您的應用程式以使用 Express**

1. 執行 `express` 命令。這會產生 `package.json`、`app.js` 以及幾個目錄。

   ```
   ~/nodejs-example-express-rds$ express
   ```

   當提示您是否要繼續時，請輸入 **y**。
**注意**  
如果 **express** 命令無法使用，您可能未依先前*先決條件*章節中的所述內容安裝 Express 命令列產生器。或者，您可能需要設定本機電腦的目錄路徑設定，才可執行 **express** 命令。如需有關設定開發環境的詳細步驟，請參閱*先決條件*章節，以繼續進行本教學課程。

1. 設定本機依存項目。

   ```
   ~/nodejs-example-express-rds$ npm install
   ```

1. (選用) 確認 Web 應用程式伺服器已啟動。

   ```
   ~/nodejs-example-express-rds$ npm start
   ```

   您應該會看到類似下列的輸出：

   ```
   > nodejs@0.0.0 start /home/local/user/node-express
   > node ./bin/www
   ```

   依預設，伺服器將會在連接埠 3000 上執行。若要進行測試，請在另一部終端機上執行 `curl http://localhost:3000`，或在本機電腦開啟瀏覽器並輸入 URL 位址 `http://localhost:3000`。

   按 **Ctrl\$1C** 來停止伺服器。

1. 使用 [**eb deploy**](eb3-deploy.md) 命令將變更部署至您的 Elastic Beanstalk 環境。

   ```
   ~/nodejs-example-express-rds$ eb deploy
   ```

1. 一旦環境為綠色且就緒，請重新整理 URL，確認其正常運作。您應看到顯示 **Welcome to Express** 的網頁。

接著，我們會更新 Express 應用程式以提供靜態檔案，並新增新的頁面。

**欲設定靜態檔案並新增新的頁面至您的 Express 應用程式**

1. 使用下列內容，在 [`.ebextensions`](ebextensions.md) 資料夾中新增第二個組態檔案：

   **`nodejs-example-express-rds/.ebextensions/staticfiles.config`**

   ```
   option_settings:
       aws:elasticbeanstalk:environment:proxy:staticfiles:
           /stylesheets: public/stylesheets
   ```

   此設定會將代理伺服器設定為在應用程式 `public` 路徑的 `/public` 資料夾內提供檔案。從代理伺服器靜態提供檔案，能夠減少應用程式負載。如需詳細資訊，請參閱本章先前所述的[靜態檔案](create_deploy_nodejs.container.md#nodejs-platform-console-staticfiles)。

1. (選用) 若要確認靜態映射的設定是否正確，請在 `nodejs-example-express-rds/app.js` 中註解靜態映射設定。這會從節點應用程式中移除映射。

   ```
   //  app.use(express.static(path.join(__dirname, 'public'))); 
   ```

   即使在註解此行後，上一個步驟 `staticfiles.config` 檔案中的靜態檔案映射應仍可成功載入樣式表。若要確認靜態檔案映射是透過代理靜態檔案組態 (而非 Express 應用程式) 載入，請移除 `option_settings:` 後的值。將其從靜態檔案組態和節點應用程式移除之後，樣式表將無法載入。

   完成測試時，請記得重設 `nodejs-example-express-rds/app.js` 及 `staticfiles.config` 的內容。

1. 新增 `nodejs-example-express-rds/routes/hike.js`。輸入下列內容：

   ```
   exports.index = function(req, res) {
    res.render('hike', {title: 'My Hiking Log'});
   };
   
   exports.add_hike = function(req, res) {
   };
   ```

1. 更新 `nodejs-example-express-rds/app.js` 以納入三個新的行。

   首先，新增下列行來為此路由添加 `require`：

   ```
   var hike = require('./routes/hike');
   ```

   您的檔案看起來如下列程式碼片段：

   ```
   var express = require('express');
   var path = require('path');
   var hike = require('./routes/hike');
   ```

   然後，請在 `nodejs-example-express-rds/app.js` 後將下列兩行新增至 `var app = express();`：

   ```
   app.get('/hikes', hike.index);
   app.post('/add_hike', hike.add_hike);
   ```

   您的檔案看起來如下列程式碼片段：

   ```
   var app = express();
   app.get('/hikes', hike.index);
   app.post('/add_hike', hike.add_hike);
   ```

1. 將 `nodejs-example-express-rds/views/index.jade` 複製至 `nodejs-example-express-rds/views/hike.jade`。

   ```
   ~/nodejs-example-express-rds$ cp views/index.jade views/hike.jade
   ```

1. 使用 [**eb deploy**](eb3-deploy.md) 命令部署變更。

   ```
   ~/nodejs-example-express-rds$ eb deploy
   ```

1. 您的環境將在幾分鐘後更新。您的環境為綠色且就緒後，請重新整理您的瀏覽器，並將 **hikes** 附加至 URL 尾端 (即 `http://node-express-env-syypntcz2q.elasticbeanstalk.com/hikes`)，藉此確認其正常運作。

   您應看到標題為 **My Hiking Log (My Hiking Log)** 的網頁。

現在，您已建立使用 Express 架構的 Web 應用程式。在下一節中，我們將修改應用程式，以使用 Amazon Relational Database Service (RDS) 儲存健行日誌。

## 更新應用程式，以使用 Amazon RDS
<a name="create_deploy_nodejs_express.add_rds"></a>

在下一個步驟中，我們將更新應用程式，以使用 Amazon RDS for MySQL。

**更新應用程式，以使用 RDS for MySQL**

1. 若要建立與 Elastic Beanstalk 環境耦合的 RDS for MySQL 資料庫，請遵循本章後續[新增資料庫](create-deploy-nodejs.rds.md)主題中的指示。新增資料庫執行個體約需要 10 分鐘。

1.  使用下列內容更新 `package.json` 中的相依性區段：

   ```
   "dependencies": {
       "async": "^3.2.4",
       "express": "4.18.2",
       "jade": "1.11.0",
       "mysql": "2.18.1",
       "node-uuid": "^1.4.8",
       "body-parser": "^1.20.1",
       "method-override": "^3.0.0",
       "morgan": "^1.10.0",
       "errorhandler": "^1.5.1"
     }
   ```

1. 執行 **npm install**。

   ```
   ~/nodejs-example-express-rds$ npm install
   ```

1. 更新 `app.js`，以便連接至資料庫、建立資料表，並插入單一預設增長日誌。每次部署此應用程式時，其會捨棄先前的增長資料表，並重新建立。

   ```
   /**
    * Module dependencies.
    */
   
    const express = require('express')
    , routes = require('./routes')
    , hike = require('./routes/hike')
    , http = require('http')
    , path = require('path')
    , mysql = require('mysql')
    , async = require('async')
    , bodyParser = require('body-parser')
    , methodOverride = require('method-override')
    , morgan = require('morgan')
    , errorhandler = require('errorhandler');
   
   const { connect } = require('http2');
   
   const app = express()
   
   app.set('views', __dirname + '/views')
   app.set('view engine', 'jade')
   app.use(methodOverride())
   app.use(bodyParser.json())
   app.use(bodyParser.urlencoded({ extended: true }))
   app.use(express.static(path.join(__dirname, 'public')))
   
   
   app.set('connection', mysql.createConnection({
   host: process.env.RDS_HOSTNAME,
   user: process.env.RDS_USERNAME,
   password: process.env.RDS_PASSWORD,
   port: process.env.RDS_PORT}));  
   
   function init() {
    app.get('/', routes.index);
    app.get('/hikes', hike.index);
    app.post('/add_hike', hike.add_hike);
   }
   
   const client = app.get('connection');
   async.series([
    function connect(callback) {
      client.connect(callback);
      console.log('Connected!');
    },
    function clear(callback) {
      client.query('DROP DATABASE IF EXISTS mynode_db', callback);
    },
    function create_db(callback) {
      client.query('CREATE DATABASE mynode_db', callback);
    },
    function use_db(callback) {
      client.query('USE mynode_db', callback);
    },
    function create_table(callback) {
       client.query('CREATE TABLE HIKES (' +
                           'ID VARCHAR(40), ' +
                           'HIKE_DATE DATE, ' +
                           'NAME VARCHAR(40), ' +
                           'DISTANCE VARCHAR(40), ' +
                           'LOCATION VARCHAR(40), ' +
                           'WEATHER VARCHAR(40), ' +
                           'PRIMARY KEY(ID))', callback);
    },
    function insert_default(callback) {
      const hike = {HIKE_DATE: new Date(), NAME: 'Hazard Stevens',
            LOCATION: 'Mt Rainier', DISTANCE: '4,027m vertical', WEATHER:'Bad', ID: '12345'};
      client.query('INSERT INTO HIKES set ?', hike, callback);
    }
   ], function (err, results) {
    if (err) {
      console.log('Exception initializing database.');
      throw err;
    } else {
      console.log('Database initialization complete.');
      init();
    }
   });
   
   module.exports = app
   ```

1. 將下列內容新增至 `routes/hike.js`。這將使路線能夠將新的增長日誌插入*增長*資料庫。

   ```
   const uuid = require('node-uuid');
   exports.index = function(req, res) {
     res.app.get('connection').query( 'SELECT * FROM HIKES', function(err,
   rows) {
       if (err) {
         res.send(err);
       } else {
         console.log(JSON.stringify(rows));
         res.render('hike', {title: 'My Hiking Log', hikes: rows});
     }});
   };
   exports.add_hike = function(req, res){
     const input = req.body.hike;
     const hike = { HIKE_DATE: new Date(), ID: uuid.v4(), NAME: input.NAME,
     LOCATION: input.LOCATION, DISTANCE: input.DISTANCE, WEATHER: input.WEATHER};
     console.log('Request to log hike:' + JSON.stringify(hike));
     req.app.get('connection').query('INSERT INTO HIKES set ?', hike, function(err) {
         if (err) {
           res.send(err);
         } else {
           res.redirect('/hikes');
         }
      });
   };
   ```

1. 將 `routes/index.js` 的內容取代為：

   ```
   /*
    * GET home page.
    */
   
   exports.index = function(req, res){
     res.render('index', { title: 'Express' });
   };
   ```

1. 將以下 Jade 範本新增至 `views/hike.jade`，以提供用於新增增長日誌的使用者介面。

   ```
   extends layout
   
   block content
     h1= title
     p Welcome to #{title}
   
     form(action="/add_hike", method="post")
       table(border="1")
         tr
           td Your Name
           td
             input(name="hike[NAME]", type="textbox")
         tr
           td Location
           td
             input(name="hike[LOCATION]", type="textbox")
         tr
           td Distance
           td
             input(name="hike[DISTANCE]", type="textbox")
         tr
           td Weather
           td
             input(name="hike[WEATHER]", type="radio", value="Good")
             | Good
             input(name="hike[WEATHER]", type="radio", value="Bad")
             | Bad
             input(name="hike[WEATHER]", type="radio", value="Seattle", checked)
             | Seattle
         tr
           td(colspan="2")
             input(type="submit", value="Record Hike")
   
     div
       h3 Hikes
       table(border="1")
         tr
           td Date
           td Name
           td Location
           td Distance
           td Weather
         each hike in hikes
           tr
             td #{hike.HIKE_DATE.toDateString()}
             td #{hike.NAME}
             td #{hike.LOCATION}
             td #{hike.DISTANCE}
             td #{hike.WEATHER}
   ```

1. 使用 [**eb deploy**](eb3-deploy.md) 命令部署變更。

   ```
   ~/nodejs-example-express-rds$ eb deploy
   ```

## 清除
<a name="create_deploy_nodejs_express.delete"></a>

如果您已完成使用 Elastic Beanstalk，您可終止環境。

使用 **eb terminate** 命令來終止您的環境及其中的所有資源。

```
~/nodejs-example-express-rds$ eb terminate
The environment "nodejs-example-express-rds-env" and all associated instances will be terminated.
To confirm, type the environment name: nodejs-example-express-rds-env
INFO: terminateEnvironment is starting.
...
```