express.jsアプリをAWS CloudFormationでデプロイする


<!--# express.jsアプリをAWS CloudFormationでデプロイする-->

CloudFormationを使用してexpressアプリをlambda functionとしてデプロイ、API gatewayを通して公開する

## express.jsでアプリを作る

まず、ふつうにアプリを書く。ファイル名はapp.jsとする

```js
// app.js
const express = require('express');
const cors = require('cors');
const app = express();
const port = 3000;

app.use(cors());

app.get('/', (req, res) => {
  console.log(req.method, req.url);
  res.send({ status: true });
});

app.listen(port, () => {
  console.log(`listening on http://localhost:${port}`);
});
```

GETで何かが返ってくるごくごく簡単なアプリ

## aws-serverless-expressの導入

- https://github.com/awslabs/aws-serverless-express/tree/master/examples/basic-starter#steps-for-running-the-example

### middlewareを使うのでpackageを導入

```sh
$ npm install --save aws-serverless-express
```

### サンプルのファイルを拝借

```sh
$ git clone https://github.com/awslabs/aws-serverless-express.git ase
$ cp -r ase/example/scripts .
$ cp ase/example/api-gateway-event.json .
$ cp ase/example/cloudformation.yaml .
$ cp ase/example/lambda.js .
$ cp ase/example/simple-proxy-api.yaml .
$ rm -rf ase
```

### サンプルのpackage.jsonからスクリプトなどを拝借

```js:package.json
  "scripts": {
    "start": "node app.local.js",
    "config": "node ./scripts/configure.js",
    "deconfig": "node ./scripts/deconfigure.js",
    "local": "node scripts/local",
    "invoke-lambda": "aws lambda invoke --function-name $npm_package_config_functionName --region $npm_package_config_region --payload file://api-gateway-event.json lambda-invoke-response.json && cat lambda-invoke-response.json",
    "create-bucket": "aws s3 mb s3://$npm_package_config_s3BucketName --region $npm_package_config_region",
    "delete-bucket": "aws s3 rb s3://$npm_package_config_s3BucketName --region $npm_package_config_region",
    "package": "aws cloudformation package --template ./cloudformation.yaml --s3-bucket $npm_package_config_s3BucketName --output-template packaged-sam.yaml --region $npm_package_config_region",
    "deploy": "aws cloudformation deploy --template-file packaged-sam.yaml --stack-name $npm_package_config_cloudFormationStackName --capabilities CAPABILITY_IAM --region $npm_package_config_region",
    "package-deploy": "npm run package && npm run deploy",
    "delete-stack": "aws cloudformation delete-stack --stack-name $npm_package_config_cloudFormationStackName --region $npm_package_config_region",
    "setup": "npm install && (aws s3api get-bucket-location --bucket $npm_package_config_s3BucketName --region $npm_package_config_region || npm run create-bucket) && npm run package-deploy"
  },
  "config": {
    "s3BucketName": "YOUR_UNIQUE_BUCKET_NAME",
    "region": "YOUR_AWS_REGION",
    "cloudFormationStackName": "YOUR_STACK_NAME",
    "functionName": "YOUR_SERVERLESS_EXPRESS_LAMBDA_FUNCTION_NAME",
    "accountId": "YOUR_ACCOUNT_ID",
    "profile": "YOUR_PROFILE"
  }
```

### configの修正

- cloudFormationStackNameはわかりやすい名前
- profileはaws-cliの使いたいprofile

これ以外のconfig項目は設定スクリプトで変更する

```js:package.json
    "cloudFormationStackName": "YOUR_STACK_NAME",
    "profile": "YOUR_PROFILE"
```

### nodejsのランタイムが古いので修正

```diff:cloudformation.yaml#L60
-     Runtime: nodejs8.10
+     Runtime: nodejs12.x
```

### APIのtitleをわかりやすいものに変更

```diff:simple-proxy-api.yaml#L4
- title: AwsServerlessExpressApi
+ title: <YourApiTitle>
```

## 設定変更スクリプトの実行

package.jsonに設定を書き換えるスクリプトが用意されているので実行

```sh
$ npm run config -- --account-id="<accountId>" --bucket-name="<bucketName>" --region="<region>"
```

- accountId→AWSのアカウントID
- bucketName→S3のバケットの名前(わかりやすい名前をつける)
- region→ap-northeast-1とか

`--function-name`オプションもあるが、指定するとかえってハマるので私は指定しなかった

## アプリの修正

### app.jsの修正

- aws-serverless-expressのmiddlewareをexpress appに食わせる
- app.listenは別ファイルに移動するため削除
- appをmoduleとしてexportしておく

```diff:app.js
+ const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware');
- const port = 3000;
  ...
+ app.use(awsServerlessExpressMiddleware.eventContext());
  ...
- app.listen(port, () => {
-   console.log('Listening on port ' + port + ' ...');
- });
  ...
+ module.exports = app;
```

### app.local.jsを追加(ローカル実行用)

修正したapp.jsのmoduleをimportしてlisten実行部分をもってくる

```js:app.local.js
const app = require('./app');
const port = 3000;

app.listen(port, () => {
  console.log(`listening on http://localhost:${port}`);
});
```

## デプロイ実行

```sh
$ npm run setup
```

問題なければ CloudFormation にスタックが追加される。スタックの詳細の「出力」タブから Lambda function、API gateway、エンドポイントにアクセスできる

### 必要ならば設定情報を破棄

```
$ npm run deconfig
```

## その他

- デプロイに失敗した場合はAWS Console の CloudFormation > スタック > イベント を見てエラー内容などを確認する
- エラーで生成に失敗したスタックは都度削除したほうがよさそう

## 参考

- https://qiita.com/hxrxchang/items/dd49ad9fbcd7e3eae823
- https://qiita.com/uramotot/items/83500a9264cb0a8d2ca6
- https://encr.jp/blog/posts/20200325_lunch/