express-generatorで生成したテンプレートプロジェクトをTypeScript実装に置換|AWSでサーバレス化
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に登録された
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
curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
sudo python get-pip.py
sudo pip install -I awscli
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のインストール
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からコピペで追加
{
"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に登録された
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"
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');
});
}
}
'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に登録された
URLにアクセス
- API Gateway→ステージ→URL の呼び出しのURLにアクセス
感想
AWSド初心者の私には、少々最初のハードルが高くて1日ぐらい掛かっちゃいました。
AWS関連のスクリプトと設定ファイルを/awsディレクトリを作成して、まとめて突っ込んだ結果、
何度やってもInternal Server Errorが表示されてしまった。
パスがおかしいんだろうけど、何度直してもエラーで表示できなかったので、
素直にaws-serverless-express/exampleと同じように、プロジェクト直下に置く構成にしたら動きました。。。
元々作ってたExpressのルーティング処理があれば、簡単にサーバレス化できそうですね。
2回目は大丈夫です。
ソース
Author And Source
この問題について(express-generatorで生成したテンプレートプロジェクトをTypeScript実装に置換|AWSでサーバレス化), 我々は、より多くの情報をここで見つけました https://qiita.com/uegaki-masaaki/items/13ddb5d702a8f748f5ad著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .