Edited at

express-generatorで生成したテンプレートプロジェクトをTypeScript実装に置換|AWSでサーバレス化

More than 1 year has passed since last update.

express-generatorで生成したテンプレートプロジェクトをTypeScript実装に置換|リファクタリングの続きです。


AWSド初心者がNode.js,Express,API Gateway,Lambdaを使ったサーバレスに挑む

せっかくNode.jsを触りだしたので、AWSでサーバレスをやってみたい。

とは言え、自力で触った事があるのは、管理コンソールでEC2とS3の操作したぐらいしか。。。

AWS周りの環境設定は何も無い状態からスタートします。IAMユーザもありません。

まずはIAMユーザの作成からスタート。


IAMユーザ追加

1. IAM→ユーザ→ユーザを追加

2. ユーザー名を入力→プログラムによるアクセス:ON→AWS マネジメントコンソールへのアクセス:ON→カスタムパスワード入力→パスワードのリセットが必要:OFF

3. グループの作成→グループ名を入力

4. 権限付与(※この権限で問題無いかは不明。。。)


  • AmazonAPIGatewayAdministrator:Provides full access to create/edit/delete APIs in Amazon API Gateway via the AWS Management Console.

  • AmazonAPIGatewayInvokeFullAccess:Provides full access to invoke APIs in Amazon API Gateway.

  • AmazonAPIGatewayPushToCloudWatchLogs:Allows API Gateway to push logs to user's account.

  • AWSLambdaFullAccess:Provides full access to Lambda, S3, DynamoDB, CloudWatch Metrics and Logs.

5. ユーザの作成


  • 認証情報のcsvをダウンロード

6. ユーザ設定

・IAM→ユーザ→作成したユーザ→認証情報→MFA デバイスの割り当て→Google Authenticatorの2段階認証コードを2連続で設定


AWS CLIをインストール


Python3をインストール

brew install pyenv

pyenv install 3.6.5
pyenv local 3.6.5
python --version

cat << 'EOS' >> ~/.bash_profile
export PYENV_ROOT=
$HOME/.pyenv
export PATH=
$PYENV_ROOT/bin:$PATH
eval "
$(pyenv init -)"
EOS


AWS CLIをインストール

curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"

sudo python get-pip.py
sudo pip install -I awscli


AWS CLI の設定

aws configure

AWS Access Key ID : CSVのアクセスキー
AWS Secret Access Key : CSVのシークレットキー
Default region name : ap-northeast-1
Default output format : json


aws-serverless-expressを導入


aws-serverless-expressのインストール

yarn add aws-serverless-express

yarn add @types/aws-serverless-express --dev


aws-serverless-expressのexampleから設定ファイルをコピー

git clone https://github.com/awslabs/aws-serverless-express.git ase

mkdir todo/scripts
cp -r ase/example/scripts todo/scripts
cp ase/example/api-gateway-event.json todo
cp ase/example/cloudformation.yaml todo
cp ase/example/lambda.js todo
cp ase/example/simple-proxy-api.yaml todo
rm -rf ase


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

yarn run config --account-id=アカウントID --bucket-name=todo-bucket9 --region=ap-northeast-1

yarn run v1.6.0
$ node ./aws/configure.js --account-id=アカウントID --bucket-name=todo-bucket9 --region=ap-northeast-1
✨ Done in 0.11s.


package.jsonにAWSのタスク追加


  • configはyarn run configで自動追加される

  • scriptsはexampleからコピペで追加


package.json

{

"name": "todo",
"version": "0.0.0",
"private": true,
+ "config": {
+ "s3BucketName": "todo-bucket9",
+ "region": "ap-northeast-1",
+ "cloudFormationStackName": "TodoServerlessExpressStack",
+ "functionName": "todo",
+ "accountId": "[CSVのアクセスキー]"
+ },
"scripts": {
"start": "nodemon ./bin/www",
"debug": "nodemon --inspect ./bin/www",
"build": "yarn run build-ts && yarn run copy-static-assets",
"build-ts": "tsc",
"tslint": "tslint -c tslint.json -p tsconfig.json",
"copy-static-assets": "ts-node copyStaticAssets.ts",
+ "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": "yarn package && yarn deploy",
+ "delete-stack": "aws cloudformation delete-stack --stack-name $npm_package_config_cloudFormationStackName --region $npm_package_config_region",
+ "setup": "npm i && (aws s3api get-bucket-location --bucket $npm_package_config_s3BucketName --region $npm_package_config_region || yarn create-bucket) && yarn package-deploy"
},
"dependencies": {
"aws-serverless-express": "^3.2.0",
"cookie-parser": "^1.4.3",
"debug": "^3.1.0",
"express": "^4.16.3",
"http-errors": "^1.6.3",
"jade": "^1.11.0",
"morgan": "^1.9.0",
"path": "^0.12.7"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.1",
"@types/moment": "^2.13.0",
"@types/morgan": "^1.7.35",
"@types/shelljs": "^0.7.8",
"nodemon": "^1.17.3",
"shelljs": "^0.8.1",
"ts-node": "^5.0.1",
"tslint": "^5.9.1",
"typescript": "^2.8.3"
}
}


Node.jsのバージョンをAWS Lambdaで利用可能な最新バージョンに合わせる


  • npm iをyarn installにしたい所だが、yarnだと後々、ClooudFormationデプロイ時にエラーが出るのでnpmでいく


docker-compose.yml

version: '3'


services:
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "80:80"
volumes:
- "./conf.d:/etc/nginx/conf.d"
links:
- node_express

node_express:
- image: node:9.11.1-alpine
+ image: node:8.11.1-alpine
container_name: node_express
hostname: node_express
volumes:
- ".:/src"
working_dir: /src
command: >
sh -c
"yarn global add typings
&& npm i
&& typings i
&& yarn build
&& yarn start"
ports:
- "3000:3000"



ソース修正


  • app.jsをTypeScript化したソースにaws-serverless-express/middlewareの利用設定を追加


src/app.ts

import { Router, NextFunction, Request, Response } from 'express';


import * as createError from 'http-errors';
import * as express from 'express';
import * as path from 'path';
import * as cookieParser from 'cookie-parser';
import * as logger from 'morgan';
+ import * as awsServerlessExpressMiddleware from 'aws-serverless-express/middleware';

import { IndexController } from './controllers/index';
import { UserController } from './controllers/user';

/**
* Application.
*
* @class App
*/
export class App {
public app: express.Application;

/**
* Bootstrap the application.
*
* @static
* @return {ng.auto.IInjectorService} Returns the newly created injector for this app.
*/
public static bootstrap(): App {
return new App();
}

/**
* Constructor.
*
* @constructor
*/
constructor() {
this.app = express();
this.setConfig();
this.setRoutes();
this.setApiRoutes();
this.setErrorHandler();
}

/**
* Configure application
*
*/
private setConfig(): void {
+ this.app.use(awsServerlessExpressMiddleware.eventContext());

this.app.set('views', path.join(__dirname, 'views'));
this.app.set('view engine', 'jade');

this.app.use(logger('dev'));

this.app.use(express.json());
this.app.use(express.urlencoded({ extended: false }));
this.app.use(cookieParser());
this.app.use(express.static(path.join(__dirname, 'public')));
}

/**
* Create and return Router.
*
*/
private setRoutes(): void {
this.app.use('/', new IndexController().create());
this.app.use('/users', new UserController().create());
}

/**
* Create REST API routes
*
*/
private setApiRoutes(): void {
}

/**
* Create Error handler
*
*/
private setErrorHandler(): void {
// Catch 404 and forward to error handler
this.app.use((req: Request, res: Response, next: NextFunction) => {
next(createError(404));
});

// Error handler
this.app.use((err: app.Error, req: Request, res: Response, next: NextFunction) => {
// Set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// Render the error page
res.status(err.status || 500);
res.render('error');
});
}
}



  • Lambdaで利用するソースのapp取得方法をbin/wwwと同様に修正


lambda.js

'use strict'

const awsServerlessExpress = require('aws-serverless-express');
- const app = require('./app')
+ const application = require('dist/app');
+ const app = application.App.bootstrap().app;

// NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this is likely
// due to a compressed response (e.g. gzip) which has not been handled correctly
// by aws-serverless-express and/or API Gateway. Add the necessary MIME types to
// binaryMimeTypes below, then redeploy (`npm run package-deploy`)
const binaryMimeTypes = [
'application/javascript',
'application/json',
'application/octet-stream',
'application/xml',
'font/eot',
'font/opentype',
'font/otf',
'image/jpeg',
'image/png',
'image/svg+xml',
'text/comma-separated-values',
'text/css',
'text/html',
'text/javascript',
'text/plain',
'text/text',
'text/xml'
]
const server = awsServerlessExpress.createServer(app, null, binaryMimeTypes);

exports.handler = (event, context) => awsServerlessExpress.proxy(server, event, context);



デプロイ

yarn run setup

・・・・

Successfully created/updated stack - todo
✨ Done in 55.88s.


デプロイ結果


  • API Gatewayに登録された

Screen Shot 2018-04-23 at 15.52.21.png


URLにアクセス


  • API Gateway→ステージ→URL の呼び出しのURLにアクセス

https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod

Screen Shot 2018-04-23 at 15.54.23.png

https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/users/user1234

Screen Shot 2018-04-23 at 15.55.47.png


感想

AWSド初心者の私には、少々最初のハードルが高くて1日ぐらい掛かっちゃいました。

AWS関連のスクリプトと設定ファイルを/awsディレクトリを作成して、まとめて突っ込んだ結果、

何度やってもInternal Server Errorが表示されてしまった。

パスがおかしいんだろうけど、何度直してもエラーで表示できなかったので、

素直にaws-serverless-express/exampleと同じように、プロジェクト直下に置く構成にしたら動きました。。。

元々作ってたExpressのルーティング処理があれば、簡単にサーバレス化できそうですね。

2回目は大丈夫です。


ソース

https://github.com/masaaki-uegaki/todo