これは、なにをしたくて書いたもの?
自分はAWS SAMでTyepScriptを使う時はほぼ自前で設定を書いているのですが、今年の2月に出ていたAWS SAMのTypeScriptサポートを
1度試しておこうかなと思い始めたので、やってみようかなと。
AWS Serverless Application Model (SAM) CLI は、TypeScript ネイティブサポートのパブリックプレビューを発表します。
今まで試してこなかった理由は、未だにプレビュー扱いだからなのですが。まあ、そこはいったんいいかなということで。
AWS SAMのTypeScriptサポート
AWS SAMのTypeScriptサポートについて、より具体的な利用イメージを書いたブログエントリーはこちら。
Building TypeScript projects with AWS SAM CLI | AWS Compute Blog
プレビュー段階なのでベータ機能を有効にする旨やプロジェクトの構成、template.yaml
のポイントなどが書かれています。
AWS SAMのTypeScriptサポートに関するドキュメントとしては、こちらになります。esbuildサポートという形でページが記述されています。
esbuild による Node.js Lambda関数の構築 (プレビュー) - AWS Serverless Application Model
esbuild自体は、こちら。
esbuild - An extremely fast JavaScript bundler
AWSのドキュメントに以下のように書かれている内容が、TypeScriptサポート(というかesbuildサポート)の実体ですね。
esbuild と共に AWS SAM CLI を使用して Node.js Lambda 関数をビルドおよびパッケージ化します。esbuild は TypeScript で記述する Lambda 関数をサポートします。
esbuild を使用して Node.js Lambda 関数を構築するには、AWS:Serverless::Function リソースに Metadata オブジェクトを追加し、BuildMethod に esbuild を指定します。sam build を実行すると、AWS SAM は esbuild を使用して、Lambda 関数コードをバンドルします。
template.yaml
のMetadata
にesbuildの設定を記述することになります。
Metadata: BuildMethod: esbuild BuildProperties: Minify: false Target: "es2020" Sourcemap: true EntryPoints: - app.ts
Metadata
でサポートされているesbuildのプロパティは、以下がリファレンスになります。
esbuild による Node.js Lambda関数の構築 (プレビュー) / Metadata プロパティ
また、esbuildによるTypeScriptサポートを使うためには、AWS SAMの設定ファイル、環境変数、コマンドライン引数のいずれかで
ベータ機能を有効化する必要があります。
esbuild による Node.js Lambda関数の構築 (プレビュー) / esbuild プレビュー機能の使用
今回は、この機能をLocalStackを使って試してみましょう。
環境
今回の環境は、こちら。
$ python3 -V Python 3.8.10 $ localstack --version 1.0.3
LocalStackを起動。
$ LAMBDA_EXECUTOR=docker-reuse localstack start
AWS CLIおよびAWS SAM CLI、加えてLocalStackの提供ツール。
$ awslocal --version aws-cli/2.7.20 Python/3.9.11 Linux/5.4.0-122-generic exe/x86_64.ubuntu.20 prompt/off $ samlocal --version SAM CLI, version 1.53.0
アプリケーションを作成する際に使用するNode.jsのバージョン。
$ node --version v16.16.0 $ npm --version 8.11.0
AWS Lambda関数としても、Node.js 16で動かすことにします。
TypeScriptサポートを使ったAWS SAMプロジェクトを作成する
では、AWS SAMプロジェクトを作成します。
sam init
相当のことを行いますが、この時にTypeScript用のテンプレートを指定します。今回は、hello-world-typescript
を選択。
Amazon API Gateway+AWS Lambdaの構成ですね。
$ samlocal init --name sam-typescript-support-hello-world --runtime nodejs16.x --app-template hello-world-typescript --package-type Zip --no-tracing
TypeScript用のテンプレートは、まだ少ないようですが。
プロジェクト内に移動。
$ cd sam-typescript-support-hello-world
生成されたディレクトリおよびファイルを確認してみます。
$ tree -a . ├── .gitignore ├── README.md ├── events │ └── event.json ├── hello-world │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .npmignore │ ├── .prettierrc.js │ ├── app.ts │ ├── jest.config.ts │ ├── package.json │ ├── tests │ │ └── unit │ │ └── test-handler.test.ts │ └── tsconfig.json └── template.yaml 4 directories, 13 files
主要なファイルの中身も見てみましょう。
hello-world/package.json
{ "name": "hello_world", "version": "1.0.0", "description": "hello world sample for NodeJS", "main": "app.js", "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", "author": "SAM CLI", "license": "MIT", "dependencies": { }, "scripts": { "unit": "jest", "lint": "eslint '*.ts' --quiet --fix", "compile": "tsc", "test": "npm run compile && npm run unit" }, "devDependencies": { "@types/aws-lambda": "^8.10.92", "@types/jest": "^27.4.0", "@types/node": "^17.0.13", "@typescript-eslint/eslint-plugin": "^5.10.2", "@typescript-eslint/parser": "^5.10.2", "esbuild": "^0.14.14", "esbuild-jest": "^0.5.0", "eslint": "^8.8.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "^27.5.0", "prettier": "^2.5.1", "ts-node": "^10.4.0", "typescript": "^4.5.5" } }
hello-world/tsconfig.json
{ "compilerOptions": { "target": "es2020", "strict": true, "preserveConstEnums": true, "noEmit": true, "sourceMap": false, "module":"es2015", "moduleResolution":"node", "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, }, "exclude": ["node_modules", "**/*.test.ts"] }
hello-world/.npmignore
tests/*
hello-world/app.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; /** * * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format * @param {Object} event - API Gateway Lambda Proxy Input Format * * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html * @returns {Object} object - API Gateway Lambda Proxy Output Format * */ export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { let response: APIGatewayProxyResult; try { response = { statusCode: 200, body: JSON.stringify({ message: 'hello world', }), }; } catch (err) { console.log(err); response = { statusCode: 500, body: JSON.stringify({ message: 'some error happened', }), }; } return response; };
hello-world/jest.config.ts
/* * For a detailed explanation regarding each configuration property and type check, visit: * https://jestjs.io/docs/configuration */ export default { transform: { '^.+\\.ts?$': 'esbuild-jest', }, clearMocks: true, collectCoverage: true, coverageDirectory: 'coverage', coverageProvider: 'v8', testMatch: ['**/tests/unit/*.test.ts'], };
hello-world/tests/unit/test-handler.test.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { lambdaHandler } from '../../app'; describe('Unit test for app handler', function () { it('verifies successful response', async () => { const event: APIGatewayProxyEvent = { httpMethod: 'get', body: '', headers: {}, isBase64Encoded: false, multiValueHeaders: {}, multiValueQueryStringParameters: {}, path: '/hello', pathParameters: {}, queryStringParameters: {}, requestContext: { accountId: '123456789012', apiId: '1234', authorizer: {}, httpMethod: 'get', identity: { accessKey: '', accountId: '', apiKey: '', apiKeyId: '', caller: '', clientCert: { clientCertPem: '', issuerDN: '', serialNumber: '', subjectDN: '', validity: { notAfter: '', notBefore: '' }, }, cognitoAuthenticationProvider: '', cognitoAuthenticationType: '', cognitoIdentityId: '', cognitoIdentityPoolId: '', principalOrgId: '', sourceIp: '', user: '', userAgent: '', userArn: '', }, path: '/hello', protocol: 'HTTP/1.1', requestId: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef', requestTimeEpoch: 1428582896000, resourceId: '123456', resourcePath: '/hello', stage: 'dev', }, resource: '', stageVariables: {}, }; const result: APIGatewayProxyResult = await lambdaHandler(event); expect(result.statusCode).toEqual(200); expect(result.body).toEqual( JSON.stringify({ message: 'hello world', }), ); }); });
hello-world/.prettierrc.js
module.exports = { semi: true, trailingComma: "all", singleQuote: true, printWidth: 120, tabWidth: 4 };
hello-world/.eslintrc.js
module.exports = { parser: "@typescript-eslint/parser", parserOptions: { ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features sourceType: "module" }, extends: [ "plugin:@typescript-eslint/recommended", // recommended rules from the @typescript-eslint/eslint-plugin "plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. ], rules: { // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs // e.g. "@typescript-eslint/explicit-function-return-type": "off", } };
hello-world/.eslintignore
node_modules
events/event.json
{ "body": "{\"message\": \"hello world\"}", "resource": "/{proxy+}", "path": "/path/to/resource", "httpMethod": "POST", "isBase64Encoded": false, "queryStringParameters": { "foo": "bar" }, "pathParameters": { "proxy": "/path/to/resource" }, "stageVariables": { "baz": "qux" }, "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, sdch", "Accept-Language": "en-US,en;q=0.8", "Cache-Control": "max-age=0", "CloudFront-Forwarded-Proto": "https", "CloudFront-Is-Desktop-Viewer": "true", "CloudFront-Is-Mobile-Viewer": "false", "CloudFront-Is-SmartTV-Viewer": "false", "CloudFront-Is-Tablet-Viewer": "false", "CloudFront-Viewer-Country": "US", "Host": "1234567890.execute-api.us-east-1.amazonaws.com", "Upgrade-Insecure-Requests": "1", "User-Agent": "Custom User Agent String", "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", "X-Forwarded-For": "127.0.0.1, 127.0.0.2", "X-Forwarded-Port": "443", "X-Forwarded-Proto": "https" }, "requestContext": { "accountId": "123456789012", "resourceId": "123456", "stage": "prod", "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", "requestTime": "09/Apr/2015:12:34:56 +0000", "requestTimeEpoch": 1428582896000, "identity": { "cognitoIdentityPoolId": null, "accountId": null, "cognitoIdentityId": null, "caller": null, "accessKey": null, "sourceIp": "127.0.0.1", "cognitoAuthenticationType": null, "cognitoAuthenticationProvider": null, "userArn": null, "userAgent": "Custom User Agent String", "user": null }, "path": "/prod/path/to/resource", "resourcePath": "/{proxy+}", "httpMethod": "POST", "apiId": "1234567890", "protocol": "HTTP/1.1" } }
template.yaml
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > sam-typescript-support-hello-world Sample SAM Template for sam-typescript-support-hello-world # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 3 Resources: HelloWorldFunction: Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties: CodeUri: hello-world/ Handler: app.lambdaHandler Runtime: nodejs16.x Architectures: - x86_64 Events: HelloWorld: Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /hello Method: get Metadata: # Manage esbuild properties BuildMethod: esbuild BuildProperties: Minify: true Target: "es2020" Sourcemap: true EntryPoints: - app.ts Outputs: # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function # Find out more about other implicit resources you can reference within SAM # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api HelloWorldApi: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" HelloWorldFunction: Description: "Hello World Lambda Function ARN" Value: !GetAtt HelloWorldFunction.Arn HelloWorldFunctionIamRole: Description: "Implicit IAM Role created for Hello World function" Value: !GetAtt HelloWorldFunctionRole.Arn
こんな感じですね。
そのままデプロイしてみる
最初にビルドしてみます。
$ samlocal build
すると、ベータ機能を有効にするかどうかを聞かれるので
Your template contains a resource with logical ID "ServerlessRestApi", which is a reserved logical ID in AWS SAM. It could result in unexpected behaviors and is not recommended. Using esbuild for bundling Node.js and TypeScript is a beta feature. Please confirm if you would like to proceed with using esbuild to build your function. You can also enable this beta feature with 'sam build --beta-features'. [y/N]:
ここでy
と答えると、ビルドが行われます。
Experimental features are enabled for this session. Visit the docs page to learn more about the AWS Beta terms https://aws.amazon.com/service-terms/. Building codeuri: /path/to/sam-typescript-support-hello-world/hello-world runtime: nodejs16.x metadata: {'BuildMethod': 'esbuild', 'BuildProperties': {'Minify': True, 'Target': 'es2020', 'Sourcemap': True, 'EntryPoints': ['app.ts']}} architecture: x86_64 functions: HelloWorldFunction Running NodejsNpmEsbuildBuilder:CopySource Running NodejsNpmEsbuildBuilder:NpmInstall Running NodejsNpmEsbuildBuilder:CleanUp Clean up action: .aws-sam/deps/04eafaf7-b6a4-4610-9814-286e40fa522e does not exist and will be skipped. Running NodejsNpmEsbuildBuilder:EsbuildBundle Running NodejsNpmEsbuildBuilder:CopyDependencies Build Succeeded Built Artifacts : .aws-sam/build Built Template : .aws-sam/build/template.yaml Commands you can use next ========================= [*] Validate SAM template: sam validate [*] Invoke Function: sam local invoke [*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch [*] Deploy: sam deploy --guided
ビルド完了。
いったん、ビルド結果を削除します。
$ rm -rf .aws-sam
このようにコマンド実行の度に確認を求められるのもなんなので、今回はsamconfig.toml
にベータ機能を有効にする設定をしておきました。
samconfig.toml
version=0.1 [default.build.parameters] beta_features = true [default.sync.parameters] beta_features = true
この部分ですね。
esbuild による Node.js Lambda関数の構築 (プレビュー) / esbuild プレビュー機能の使用
なお、ドキュメントに書かれているような以下の内容で素直に書くと
[default.build.parameters] beta_features = true [default.sync.parameters] beta_features = true
version
が記載されていないので以下のようにエラーになります。
$ yes | samlocal sync --stack-name $(uuidgen) --region us-east-1 --no-dependency-layer Error: Error reading configuration: 'version' key is not present or is in unrecognized format.
では、デプロイしてみます。
$ yes | samlocal sync --stack-name $(uuidgen) --region us-east-1 --no-dependency-layer
デプロイが完了しました。
Deploying with following values =============================== Stack name : 36149b33-72d1-4e57-a136-6c7f3968f14f Region : us-east-1 Disable rollback : False Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-3777f670 Capabilities : ["CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"] Parameter overrides : {} Signing Profiles : null Initiating deployment ===================== 2022-08-04 01:38:14 - Waiting for stack create/update to complete CloudFormation events from stack operations (refresh every 0.5 seconds) --------------------------------------------------------------------------------------------------------------------------------------------------------------------- ResourceStatus ResourceType LogicalResourceId ResourceStatusReason --------------------------------------------------------------------------------------------------------------------------------------------------------------------- CREATE_COMPLETE AWS::CloudFormation::Stack HelloWorldFunctionRole - CREATE_COMPLETE AWS::CloudFormation::Stack ServerlessRestApi - CREATE_COMPLETE AWS::CloudFormation::Stack HelloWorldFunction - CREATE_COMPLETE AWS::CloudFormation::Stack HelloWorldFunctionHelloWorldPermissionP - rod UPDATE_COMPLETE AWS::CloudFormation::Stack HelloWorldFunctionRole - CREATE_COMPLETE AWS::CloudFormation::Stack ServerlessRestApiDeployment47fc2d5f9d - UPDATE_COMPLETE AWS::CloudFormation::Stack ServerlessRestApi - UPDATE_COMPLETE AWS::CloudFormation::Stack HelloWorldFunction - CREATE_COMPLETE AWS::CloudFormation::Stack HelloWorldFunctionHelloWorldPermissionP - rod CREATE_COMPLETE AWS::CloudFormation::Stack ServerlessRestApiProdStage - CREATE_COMPLETE AWS::CloudFormation::Stack ServerlessRestApiDeployment47fc2d5f9d - UPDATE_COMPLETE AWS::CloudFormation::Stack ServerlessRestApiProdStage - CREATE_COMPLETE AWS::CloudFormation::Stack 36149b33-72d1-4e57-a136-6c7f3968f14f - --------------------------------------------------------------------------------------------------------------------------------------------------------------------- CloudFormation outputs from deployed stack ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Outputs ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Key HelloWorldApi Description API Gateway endpoint URL for Prod stage for Hello World function Value https://3lt1ixk1uk.execute-api.amazonaws.com:4566/Prod/hello/ Key HelloWorldFunction Description Hello World Lambda Function ARN Value arn:aws:lambda:us-east-1:000000000000:function:36149b33-72d1-4e57-a136-6c7f3968f14-HelloWorldFunction-cb0cac49 Key HelloWorldFunctionIamRole Description Implicit IAM Role created for Hello World function Value arn:aws:iam::000000000000:role/36149b33-72d1-4e57-a136-6c7f396-HelloWorldFunctionRole-722273d9 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Stack creation succeeded. Sync infra completed.
Amazon API GatewayのREST APIのIDを取得して
$ REST_API_ID=$(awslocal apigateway get-rest-apis --query 'reverse(sort_by(items[], &createdDate))[0].id' --output text)
動作確認…したら、1回目はタイムアウト…。
$ curl http://localhost:4566/restapis/$REST_API_ID/Prod/_user_request_/hello {"errorMessage": "2022-08-03T16:41:02.682Z 997ef993-61d0-15cc-05dc-b9b69b20c6ad Task timed out after 3.00 seconds"}
もう1度アクセスしたら、確認できました。
$ curl http://localhost:4566/restapis/$REST_API_ID/Prod/_user_request_/hello {"message":"hello world"}
まずは、デプロイ完了ですね。
もう少しカスタマイズしてみる
これで終わってもなんなので、生成されたプロジェクトを変更していってみましょう。
$ cd hello-world
依存ライブラリをインストール。これで、package-lock.json
も作成されます。
$ npm i
テストも確認。
$ npm test
こんな感じになりました。
> hello_world@1.0.0 test > npm run compile && npm run unit > hello_world@1.0.0 compile > tsc > hello_world@1.0.0 unit > jest PASS tests/unit/test-handler.test.ts Unit test for app handler ✓ verifies successful response (3 ms) ----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------|---------|----------|---------|---------|------------------- All files | 100 | 88.88 | 100 | 100 | app.ts | 100 | 88.88 | 100 | 100 | 8 ----------|---------|----------|---------|---------|------------------- Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 0.742 s Ran all test suites.
コンパイル用のコマンドも用意されているんですね。
$ npm run compile > hello_world@1.0.0 compile > tsc
lint。
$ npm run lint > hello_world@1.0.0 lint > eslint '*.ts' --quiet --fix
なんとなく、AWS Lambda関数本体はsrc
ディレクトリに移すことにしました。
$ mkdir src $ mv app.ts src/
template.yaml
のCodeUri
は変更しません。CodeUri
で指定したディレクトリ直下に、package.json
やpackage-lock.json
が配置されていないと
困るので。
HelloWorldFunction: Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties: CodeUri: hello-world/ Handler: app.lambdaHandler
代わりに、Metadata
/EntryPoints
でsrc
配下であることを指定。また、package-lock.json
も生成されたのでUseNpmCi
もTrue
にして
おきます。
Metadata: # Manage esbuild properties BuildMethod: esbuild BuildProperties: Minify: true Target: "es2020" Sourcemap: true EntryPoints: - src/app.ts UseNpmCi: True
package.json
のscripts
も少しカスタマイズして、Prettierを使えるようにしたり、TypeScriptのビルドで--watch
できるようにしておきました。
"scripts": { "unit": "jest", "lint": "eslint '*.ts' --quiet --fix", "compile": "tsc", "compile:watch": "tsc --watch", "test": "npm run compile && npm run unit", "format": "prettier --write src tests" },
tsconfig.json
では以下のようにnoEmit
がtrue
になっているので、tsc
でTypeScriptをビルドしてもJavaScriptファイルが生成されることは
ありません。
"noEmit": true,
TypeScriptからJavaScriptを生成する役割を負うのは、AWS SAMにより実行されるesbuildです。もともと定義されているcompile
、そして
今回追加したようなtsc
と--watch
の組み合わせで、型のチェックなどを行うようにするという感じですね。
esbuildは、型情報の確認を行わないので。
次に、ソースコードも変えていきましょう。
ひとつ、TypeScriptファイルを追加します。
src/decorate.ts
export const decorateMessage = (message: string) => { return `★★★ ${message} ★★★`; };
このファイルを使うように、app.ts
を修正します。
src/app.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { decorateMessage } from './decorate'; /** * * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format * @param {Object} event - API Gateway Lambda Proxy Input Format * * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html * @returns {Object} object - API Gateway Lambda Proxy Output Format * */ export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { let response: APIGatewayProxyResult; try { response = { statusCode: 200, body: JSON.stringify({ message: decorateMessage( event.queryStringParameters != null ? (event.queryStringParameters['message'] as string) : 'hello world', ), }), }; } catch (err) { console.log(err); response = { statusCode: 500, body: JSON.stringify({ message: 'some error happened', }), }; } return response; };
追加したファイルに渡すパラメーターは、QueryStringから取得するようにしています。
body: JSON.stringify({ message: decorateMessage( event.queryStringParameters != null ? (event.queryStringParameters['message'] as string) : 'hello world', ), }),
これに合わせて、テストコードも修正。
tests/unit/test-handler.test.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { lambdaHandler } from '../../src/app'; describe('Unit test for app handler', function () { it('verifies successful response', async () => { const event: APIGatewayProxyEvent = { httpMethod: 'get', body: '', headers: {}, isBase64Encoded: false, multiValueHeaders: {}, multiValueQueryStringParameters: {}, path: '/hello', pathParameters: {}, queryStringParameters: { message: 'Hello TypeScript!!' }, requestContext: { accountId: '123456789012', apiId: '1234', authorizer: {}, httpMethod: 'get', identity: { accessKey: '', accountId: '', apiKey: '', apiKeyId: '', caller: '', clientCert: { clientCertPem: '', issuerDN: '', serialNumber: '', subjectDN: '', validity: { notAfter: '', notBefore: '' }, }, cognitoAuthenticationProvider: '', cognitoAuthenticationType: '', cognitoIdentityId: '', cognitoIdentityPoolId: '', principalOrgId: '', sourceIp: '', user: '', userAgent: '', userArn: '', }, path: '/hello', protocol: 'HTTP/1.1', requestId: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef', requestTimeEpoch: 1428582896000, resourceId: '123456', resourcePath: '/hello', stage: 'dev', }, resource: '', stageVariables: {}, }; const result: APIGatewayProxyResult = await lambdaHandler(event); expect(result.statusCode).toEqual(200); expect(result.body).toEqual( JSON.stringify({ message: '★★★ Hello TypeScript!! ★★★', }), ); }); });
app.ts
を移動したのでそれに合わせ、
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { lambdaHandler } from '../../src/app';
QueryStringの追加と
path: '/hello', pathParameters: {}, queryStringParameters: { message: 'Hello TypeScript!!' },
except
の修正。
expect(result.statusCode).toEqual(200); expect(result.body).toEqual( JSON.stringify({ message: '★★★ Hello TypeScript!! ★★★', }), );
フォーマットして
$ npm run format
テストを実行して確認。
$ npm test
OKだったので、ひとつ上のディレクトリに戻って
$ cd ..
デプロイ。
$ yes | samlocal sync --stack-name $(uuidgen) --region us-east-1 --no-dependency-layer
REST APIのIDを取得して
$ REST_API_ID=$(awslocal apigateway get-rest-apis --query 'reverse(sort_by(items[], &createdDate))[0].id' --output text)
動作確認。
$ curl http://localhost:4566/restapis/$REST_API_ID/Prod/_user_request_/hello?message=Hello {"message":"★★★ Hello ★★★"}
OKですね。
とりあえず、こんなところでしょうか。
まとめ
プレビュー段階ですが、AWS SAMのTypeScriptサポートを試してみました。
ベータ機能を有効にする必要があったり、テンプレートから生成されたプロジェクトはいろいろ変更した方が良さそうですが、とっかかりとしては
良いかなと。