Expressで作ったAPIをaws-serverless-expressでサーバレス化した話

## はじめに

最近Expressで作ったAPIを、aws-serverless-expressを使ってサーバレス化したときの手順をまとめる。

今回はExpressをサーバレス化する話なので、Expressを使ったAPIの構築については触れない。

## 前提

- Expressで既にAPIを作っている
- AWSアカウントを持っている
- aws-cliが設定済みである

## aws-serverless-expressとは

awslabが出してる、Expressで作ったRESTful APIをサーバレス化するツール。
サーバレスAPIは、Express + Lambda + API Gatewayを用いて構築する。

このツールの凄いところは、アプリケーションでいじるところが殆ど無いところだ。
なので、既にExpressでAPIサーバを構築している人なら、少しの手順を踏むだけで、サーバレスなAPIを簡単に構築することができる。

細かいことはGithubのREADMEに書いてある。
天下のAWSさんが出しているだけあって、exampleもちゃんとしてる。
1からAPIを作る人はexampleをコピーするところから始めると良いと思う。

Githubのリンクは[こちら](https://github.com/awslabs/aws-serverless-express)。

## サーバレス化

### 1. aws-serverless-expressのインストール

以下を実行して、aws-serverless-expressをインストール。

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

### 2. exampleのpackage.jsonのscriptsをコピー

aws-serverless-expressのexampleには、既に便利なscriptが用意されているため、今回はそれを利用する。

自分のプロジェクトのpackage.jsonに以下をコピペする。
(Windows以外の人はwin-config以下はコピペする必要なし)。

```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",
    "win-config": "npm run config",
    "win-deconfig": "npm run deconfig",
    "win-local": "npm run local",
    "win-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",
    "win-create-bucket": "aws s3 mb s3://%npm_package_config_s3BucketName% --region %npm_package_config_region%",
    "win-delete-bucket": "aws s3 rb s3://%npm_package_config_s3BucketName% --region %npm_package_config_region%",
    "win-package": "aws cloudformation package --template ./cloudformation.yaml --s3-bucket %npm_package_config_s3BucketName% --output-template packaged-sam.yaml --region %npm_package_config_region%",
    "win-deploy": "aws cloudformation deploy --template-file packaged-sam.yaml --stack-name %npm_package_config_cloudFormationStackName% --capabilities CAPABILITY_IAM --region %npm_package_config_region%",
    "win-package-deploy": "npm run win-package && npm run win-deploy",
    "win-delete-stack": "aws cloudformation delete-stack --stack-name %npm_package_config_cloudFormationStackName% --region %npm_package_config_region%",
    "win-setup": "npm install && (aws s3api get-bucket-location --bucket %npm_package_config_s3BucketName% --region %npm_package_config_region% || npm run win-create-bucket) && npm run win-package-deploy"
  }
```

次に、exampleで使用しているscriptファイルや、yamlファイルをコピーする必要がある。
プロジェクトルートで以下を実行。

```bash
$ 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
```

### 3. awsアカウントとs3バケットの設定

先程コピーしたスクリプトを使用して、awsアカウントとs3バケットの設定を行う。

```bash
$ npm run config -- \
--account-id="<accountId>" \
--bucket-name="<bucketName>" \
--region="<region>" \            # optinal
--function-name="<functionName>" # optinal
```

このコマンドの実行が成功すると、以下の3つのファイルが書き換わる。

- package.json
- simple-proxy-api.yaml
- cloudformation.yaml

### 4. app.jsの修正

Expressをaws-serverless-expressで動かすには少しコードに手を加えなくてはならない。

まず、app.jsでaws-serverless-expressのMiddlewareを有効化する必要がある。
以下をapp.jsに追加

```diff
const express = require('express')
+ const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware')

const app = express()

+ app.use(awsServerlessExpressMiddleware.eventContext())
```

次に、aws-serverless-expressではUNIXドメインソケットで別の箇所でlistenしているらしいので、app.jsに書いてるlistenメソッドは削除する必要がある。

```diff
- app.listen()
```

最後に、lambda.jsでapp.jsを読み込む必要があるため、app.jsをmodule化する必要がある。

以下の一文をapp.jsの末尾に追加

```diff
+ module.exports = app
```

### 5. lambda.jsの修正

exampleからコピーした、lambda.jsを修正する。
先程修正したapp.jsのパスを指定して、importするだけで良い。

```diff
const awsServerlessExpress = require('aws-serverless-express')
- const app = require('./app')
+ const app = require('./path/to/app')
```

### 6. デプロイ

exampleで用意されているsetupスクリプトを実行するだけで良い。

```bash
$ npm run setup
```

setupスクリプトを実行すると以下が順番に実行される。

1. npm install
2. デプロイ先のs3バケットの作成
3. CloudFormationのテンプレートの作成
4. CloudFormationのデプロイ

`3.` で生成されるCloudFormationのテンプレートはpackaged-sam.yamlというファイルに出力される。
packaged-sam.yamlの内容は以下の通り

1. API Gatewayの構築
2. Lambda Functionの作成
3. Lambdaの実行権限のあるIAMロールの作成

setupスクリプトの実行が成功していれば、CloudFormationのStackが実行されているはずなので、AWSコンソールにログインして、CloudFormationが実行されているか確認する。

### 7. 確認

AWSコンソールにサインインし、API Gatewayサービスを選択。
新しくAPIが作成されていることを確認する。

新しく作成されたAPIを選択し、「ステージ > prod > URLの呼び出し」に書かれているURLをBase URLとしてAPIを叩いてみる。

Expressで予め作成したAPIを実行して、動くことを確認する。
以下は例。

```bash
$ curl http://hogehoge.execute-api.ap-northeast-1.amazonaws.com/prod/v1/fuga
```

## まとめ

Expressを使って既に動いているAPIを、たった7つのステップで、簡単にサーバレス化できてしまった。

これからサーバレス、マイクロサービスがトレンドになる中、簡単に既存のAPIをサーバレス化できてしまう仕組みは本当に凄いと思った。流石AWSさん。

API Gatewayの作成まではできたので、後は独自ドメインにしたりhttpsを有効化したり、色々カスタマイズすると良いと思う。
自分はめんどくさがって手動でポチポチ設定してしまったが、本当はこの辺もCloudFormationでできるはずなので、暇になったら設定してみる。