[Sy] Express で作った API をまるっとサーバレスにする手順(aws-serverless-express使用)
これまで Express を使ってサーバサイド(API)を開発した場合、そのまま EC2 にデプロイしてたんですが、今年に入ってサーバレスに取り組んでます。今回は aws-serverless-express という AWS公式のモジュールを使って Express をまるっとサーバレス化する手順をまとめました。
2019/05/17 追記:この記事で作った API を git で管理する手順はこちらにまとめてます。
⇒ [Sy] aws-serverless-express を使ってサーバレス化した API を git で管理する1. expressを使って通常どおりローカルでAPIとして動く状態まで作る
まずはローカルで普通に Express を使って、以下のように API を作っていきます。
1-1. Express と必要なパッケージをインストール
Express をインストール。
$ yarn add express
CORS対応したいので、その辺を簡単にできる cors
をインストール。
$ yarn add cors
ローカル開発時の環境変数設定のために dotenv
をインストール。
$ yarn add dotenv
1-2. .env を作成
今回はポート番号のみになりますが、 .env
を作成して以下のようにしておきます。
(.env)
PORT=3333
1-3. app.js を作成
app.js を作って処理を書いていきます。
(app.js)
const express = require("express");
const cors = require('cors');
const dotenv = require('dotenv');
const app = express();
dotenv.config();
app.use(cors());
app.get("/", (req, res) => {
console.log("-- / --");
res.send({ status: true });
});
app.listen(process.env.PORT, () =>
console.log("Listening on port " + process.env.PORT + " ...")
);
1-4. 起動して確認
app.js を実行します。
$ node app.js
Listening on port 3333 ...
3333ポートで動き続けるので、もう一つターミナルを起動して、APIをコールします。(ブラウザやhttpクライアントからURLを打ち込んでもOKです)
$ curl http://localhost:3333
{"status":true}%
できました。これをサーバレスにしていきます。
起動していた app.js は一旦 ctrl + C
で終了しておきましょう。
$ node app.js
Listening on port 3333 ...
^C ←(ctrl + C で終了)
ここまでのディレクトリ構成はこうなってます。(yarn.lock
と package-lock.json
は省いてます)
/
├ .env
├ app.js
├ node_modules
└ package.json
ひとまず、Express を使って API を作るところまで完了です。
2. aws-serverless-expressをセットアップ
ここからが本題です。 aws-serverless-express
をインストールして、必要な設定ファイルを作ったりしていきます。
今回使った aws-serverlss-express
のバージョンは 3.3.6
です。
こちらを参考にさせていただきました。ありがとうございます。
2-1. aws-serverless-express をインストール
まずは aws-serverless-express
をインストールします。
$ yarn add aws-serverless-express
続けて、以下のように必要なyamlファイル等を awslabs
のリポジトリからいただいて配置していきます。
$ git clone https://github.com/awslabs/aws-serverless-express.git ase
$ cp -r ase/examples/basic-starter/scripts .
$ cp ase/examples/basic-starter/api-gateway-event.json .
$ cp ase/examples/basic-starter/cloudformation.yaml .
$ cp ase/examples/basic-starter/lambda.js .
$ cp ase/examples/basic-starter/simple-proxy-api.yaml .
$ rm -rf ase
2-2. package.jsonに追記
config
と scripts
を以下のように package.json
に追記します。(参考記事で紹介されている設定に加えて、 profile
によって AWS CLI の profile の設定を切り替えられるようにしています)
(package.json)
{
"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"
},
"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 --profile $npm_package_config_profile && cat lambda-invoke-response.json",
"create-bucket": "aws s3 mb s3://$npm_package_config_s3BucketName --region $npm_package_config_region --profile $npm_package_config_profile",
"delete-bucket": "aws s3 rb s3://$npm_package_config_s3BucketName --region $npm_package_config_region --profile $npm_package_config_profile",
"package": "aws cloudformation package --template ./cloudformation.yaml --s3-bucket $npm_package_config_s3BucketName --output-template packaged-sam.yaml --region $npm_package_config_region --profile $npm_package_config_profile",
"deploy": "aws cloudformation deploy --template-file packaged-sam.yaml --stack-name $npm_package_config_cloudFormationStackName --capabilities CAPABILITY_IAM --region $npm_package_config_region --profile $npm_package_config_profile",
"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 --profile $npm_package_config_profile",
"setup": "npm install && (aws s3api get-bucket-location --bucket $npm_package_config_s3BucketName --region $npm_package_config_region --profile $npm_package_config_profile || npm run create-bucket) && npm run package-deploy"
},
・
・
ここで、 cloudFormationStackName
と profile
は、直接書き直しておきます。
cloudFormationSatckName
は、 CloudFormation に作成されるスタックの名前の指定になります。自分がわかる名前であればなんでも良いかと。
profile
は、 AWS CLI の config で設定している profile のうち、どれを使うかの指定になります。
例えばこんな感じになります。
(package.json)
{
"config": {
・
・
"cloudFormationStackName": "ExpressServerlessTestApiStack",
・
・
"profile": "utano320"
},
・
・
2-3. simple-proxy-api.yaml変更
先程 awslabs
からコピーしてきた yamlファイルのうち、 simple-proxy-api.yaml
は一箇所手を入れておく必要があるので、変更しておきます。
変更が必要な箇所は、 info -> title
です。ここで設定した名前は Amazon API Gateway
上に登録される API の名前になります。
コピーしてきた段階だと、ここが AwsServerlessExpressApi
となってます。このままでも動くんですが、今後同じ手順で複数の API を作っていく時に名前がかぶってしまうと上書きされてしまうので、名前を変えておく方が良いです。
例えば、こんな感じですね。(cloudFormationStackNameと合わせて自分なりにルールを決めておくとわかりやすいと思います)
---
swagger: 2.0
info:
title: ExpressServerlessTestApi
・
・
これで aws-serverless-experss
まわりでやることは終わりです。
3. ローカルでも Lambda 上でも動くように app.js を変更
いよいよ AWS 上(API Gateway + Lambda)で動くようにしていきます。
今後の開発はローカルで普通どおりやりたいので、 ローカルでもサーバレスでも同じコードで動く 状態に app.js を変更します。
以下の「追記」「変更」の箇所を修正してください。
const app = express();
// ↓追記
const isProd = () => {
return !!process.env.AWS_REGION
}
// ↑追記
// ↓変更
// dotenv.config();
if (isProd()) {
console.log('Environment: Production');
const awsServerlessExpressMiddleware = require("aws-serverless-express/middleware");
app.use(awsServerlessExpressMiddleware.eventContext());
} else {
dotenv.config();
}
// ↑変更
・
・
// ↓変更
// app.listen(process.env.PORT, () =>
// console.log("Listening on port " + process.env.PORT + " ...")
// );
if (isProd()) {
module.exports = app;
} else {
app.listen(process.env.PORT, () =>
console.log("Listening on port " + process.env.PORT + " ...")
);
}
// ↑変更
一つずつ説明を。
isProd()
は、Lambda上で実行される際に環境変数 AWS_REGION
が設定されてたので、それを利用して設定されていればProduction(Lambda上で実行)と判定する関数です。
const isProd = () => {
return !!process.env.AWS_REGION
}
次に、以下の部分では、ローカルの場合 dotenv
を使ってポートの設定など .env
に書かれた設定を有効化し、Lambdaで実行された場合( isProd()
が true
の場合)は aws-serverless-express/middleware
を読み込んでミドルウェアをセットしてます。
if (isProd()) {
console.log('Environment: Production');
const awsServerlessExpressMiddleware = require("aws-serverless-express/middleware");
app.use(awsServerlessExpressMiddleware.eventContext());
} else {
dotenv.config();
}
最後にこの部分は、ローカルの場合はWebサーバとして起動、Lambdaで実行された場合は app
をモジュール化してます。
if (isProd()) {
module.exports = app;
} else {
app.listen(process.env.PORT, () =>
console.log("Listening on port " + process.env.PORT + " ...")
);
}
4. 設定の書き換え用スクリプト実行
awslabs
から落としてきたスクリプトを以下のコマンドで実行し、 yamlファイルと package.json
を書き換えていきます。
コマンドに渡すパラメータは、それぞれ自分の環境に合わせて変えてください。
account-id
AWSのアカウントIDです。12桁くらいの数字になります。AWSコンソールにログインして「マイアカウント」で確認できます)
bucket-name
S3上に作るバケット名です。全ユーザでユニークである必要があります。事前に作っておく必要はありません。このバケットにコードなどがアップロードされます。
region
リージョンの指定です。今回は例として東京リージョン(ap-northeast-1)としてます。(わからない場合は、AWSコンソールにログインして、リージョンを切り替えた際にURLにある region=xx-xxxxxxxx-xx
の部分を参考にしてください)
function-name
作成される Lambda の関数名になります。
上記の値がそろったら、以下のコマンドを実行します。
$ npm run config -- \
--account-id="XXXXXXXXXXXX" \
--bucket-name="utano320-express-serverless-test-api" \
--region="ap-northeast-1" \
--function-name="ExpressServerlessTest"
5. サーバレス化
すべての準備が整ったので、以下のコマンドを実行してAWS上にAPIを作ります。
$ npm run setup
・
・
・
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - ExpressServerlessTestApiStack
これで Express を使った API をサーバレス化することができました。
エンドポイントは、以下の手順で AWSコンソールの Amazon API Gateway
から取得できます。
Amazon API Gateway
⇒ 作成したAPI
⇒ ステージ
⇒ prod
⇒ URLの呼び出し