これは、なにをしたくて書いたもの?
前に、プレビュー版のAWS SAMのTypeScriptサポートを試してみました。
AWS SAMのTypeScriptサポート(プレビュー)をLocalStackで試す - CLOVER🍀
そして、ふとAWS SAMのTypeScriptに関するページを見ると、「プレビュー」という文字がなくなっていたことに気づきまして。
esbuild による Node.js Lambda関数の構築 - AWS Serverless Application Model
いつの間にか、プレビューではなくなりました?
というわけで、確認してみました。
AWS SAMのTypeScriptサポート
ドキュメント上はTypeScriptのサポートと書いているわけではなく、esbuildのサポートということになっています。
esbuild による Node.js Lambda関数の構築 - AWS Serverless Application Model
esbuildがTypeScriptを使えるので、esbuildサポートのGAに伴いTypeScriptも正式使えるようになったという感じですね。
Node.js AWS Lambda 関数を構築してパッケージ化する場合、AWS SAM CLI と esbuild JavaScript バンドラーを使用できます。esbuild バンドラーは TypeScript で記述されて Lambda 関数をサポートします。
プレビューの時と同じように、Metadata
のBuildMethod
にesbuild
を指定すれば使えるようです。
アナウンスもありました。
AWS SAM CLI esbuild のサポート、一般提供を開始
前にプレビューを試した後の、1ヶ月後ですね…。もう1年経っています…。
バージョンとしては、1.56.0からのサポートのようです。
Release Release 1.56.0 - esbuild GA, Event-based Metrics · aws/aws-sam-cli · GitHub
今回は、こちらをLocalStackを使って試してみます。
環境
今回の環境は、こちら。
$ python3 -V Python 3.10.12 $ localstack --version 2.2.0
起動。
$ localstack start
AWS CLIおよびAWS SAM CLIの、LocalStack提供版。
$ awslocal --version aws-cli/2.13.22 Python/3.11.5 Linux/5.15.0-84-generic exe/x86_64.ubuntu.22 prompt/off $ samlocal --version SAM CLI, version 1.82.0
アプリケーションを作成する際に使用する、Node.jsのバージョン。
$ node --version v18.18.0 $ npm --version 9.8.1
AWS Lambda関数としても、Node.js 18で動作させます。
TypeScriptサポートを使ったAWS SAMプロジェクトを作成する
では、TypeScriptを使ったAWS SAMプロジェクトを作成します。
Node.js 18のTypeScriptのテンプレートはこちら。
$ curl -s https://raw.githubusercontent.com/aws/aws-sam-cli-app-templates/master/manifest-v2.json | jq '."nodejs18.x"[] | select(.appTemplate | test("typescript"))' { "directory": "nodejs18.x/hello-ts", "displayName": "Hello World Example TypeScript", "dependencyManager": "npm", "appTemplate": "hello-world-typescript", "packageType": "Zip", "useCaseName": "Hello World Example" } { "directory": "nodejs18.x/hello-ts-pt", "displayName": "Hello World Example TypeScript With Powertools for AWS Lambda", "dependencyManager": "npm", "appTemplate": "hello-world-powertools-typescript", "packageType": "Zip", "useCaseName": "Hello World Example with Powertools for AWS Lambda" }
今回はhello-world-typescript
を使いましょう。Amazon API Gateway+AWS Lambdaの構成です。
$ samlocal init --name sam-typescript-support-ga-hello-world --runtime nodejs18.x --app-template hello-world-typescript --package-type Zip --no-tracing --no-application-insights
プロジェクト内に移動。
$ cd sam-typescript-support-ga-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 ├── samconfig.toml └── template.yaml 4 directories, 14 files
主なファイルの中身を確認してみます。
samconfig.toml
# More information about the configuration file can be found here: # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html version = 0.1 [default] [default.global.parameters] stack_name = "sam-typescript-support-ga-hello-world" [default.build.parameters] cached = true parallel = true [default.validate.parameters] lint = true [default.deploy.parameters] capabilities = "CAPABILITY_IAM" confirm_changeset = true resolve_s3 = true [default.package.parameters] resolve_s3 = true [default.sync.parameters] watch = true [default.local_start_api.parameters] warm_containers = "EAGER" [default.local_start_lambda.parameters] warm_containers = "EAGER"
template.yaml
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > sam-typescript-support-ga-hello-world Sample SAM Template for sam-typescript-support-ga-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: nodejs18.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
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", "scripts": { "unit": "jest", "lint": "eslint '*.ts' --quiet --fix", "compile": "tsc", "test": "npm run compile && npm run unit" }, "dependencies": { "esbuild": "^0.14.14" }, "devDependencies": { "@types/aws-lambda": "^8.10.92", "@types/jest": "^29.2.0", "@types/node": "^18.11.4", "@typescript-eslint/eslint-plugin": "^5.10.2", "@typescript-eslint/parser": "^5.10.2", "eslint": "^8.8.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "^29.2.1", "prettier": "^2.5.1", "ts-jest": "^29.0.5", "ts-node": "^10.9.1", "typescript": "^4.8.4" } }
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/.prettierrc.js
module.exports = { semi: true, trailingComma: "all", singleQuote: true, printWidth: 120, tabWidth: 4 };
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?$': 'ts-jest', }, clearMocks: true, collectCoverage: true, coverageDirectory: 'coverage', coverageProvider: 'v8', testMatch: ['**/tests/unit/*.test.ts'], };
hello-world/.npmignore
tests/*
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 .aws-sam
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> => { try { return { statusCode: 200, body: JSON.stringify({ message: 'hello world', }), }; } catch (err) { console.log(err); return { statusCode: 500, body: JSON.stringify({ message: 'some error happened', }), }; } };
hello-world/tests/unit/test-handler.test.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { lambdaHandler } from '../../app'; import { expect, describe, it } from '@jest/globals'; 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', }), ); }); });
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
のこちらですね。
Metadata: # Manage esbuild properties BuildMethod: esbuild BuildProperties: Minify: true Target: "es2020" Sourcemap: true EntryPoints: - app.ts
デプロイしてみる
とりあえずデプロイしてみましょう。
package-lock.json
がなかったので、最初にnpm install
しておきます。
$ cd hello-world $ npm i $ cd ..
ビルド。
$ samlocal build
問題なく終了。
Starting Build use cache Manifest file is changed (new hash: 2188d26bd084f69dde27148437a18531) or dependency folder (.aws-sam/deps/0af770b6-5c88-49f1-9a49-c86cac821b1e) is missing for (HelloWorldFunction), downloading dependencies and copying/building source Building codeuri: /path/to/sam-typescript-support-ga-hello-world/hello-world runtime: nodejs18.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:EsbuildBundle Running NodejsNpmEsbuildBuilder:CleanUp Running NodejsNpmEsbuildBuilder:MoveDependencies Sourcemap set without --enable-source-maps, adding --enable-source-maps to function HelloWorldFunction NODE_OPTIONS You are using source maps, note that this comes with a performance hit! Set Sourcemap to false and remove NODE_OPTIONS: --enable-source-maps to disable source maps. 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
以前はここで、こんな感じでベータ機能を有効にするかどうかを聞かれていました。
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]:
デプロイ。
$ samlocal deploy --region us-east-1
完了しました。
Waiting for changeset to be created.. CloudFormation stack changeset ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Operation LogicalResourceId ResourceType Replacement ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Add HelloWorldFunctionHelloWorldPermissionProd AWS::Lambda::Permission N/A + Add HelloWorldFunctionRole AWS::IAM::Role N/A + Add ServerlessRestApiProdStage AWS::ApiGateway::Stage N/A + Add ServerlessRestApiDeployment47fc2d5f9d AWS::ApiGateway::Deployment N/A + Add ServerlessRestApi AWS::ApiGateway::RestApi N/A + Add HelloWorldFunction AWS::Lambda::Function N/A ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Changeset created successfully. arn:aws:cloudformation:us-east-1:000000000000:changeSet/samcli-deploy1695906178/cd54aa61 Previewing CloudFormation changeset before deployment ====================================================== Deploy this changeset? [y/N]: y 2023-09-28 22:03:01 - Waiting for stack create/update to complete CloudFormation events from stack operations (refresh every 5.0 seconds) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ResourceStatus ResourceType LogicalResourceId ResourceStatusReason ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- CREATE_COMPLETE AWS::IAM::Role HelloWorldFunctionRole - CREATE_COMPLETE AWS::Lambda::Function HelloWorldFunction - CREATE_COMPLETE AWS::ApiGateway::RestApi ServerlessRestApi - CREATE_COMPLETE AWS::Lambda::Permission HelloWorldFunctionHelloWorldPermissionProd - CREATE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f9d - CREATE_COMPLETE AWS::ApiGateway::Stage ServerlessRestApiProdStage - CREATE_COMPLETE AWS::CloudFormation::Stack sam-typescript-support-ga-hello-world - ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- CloudFormation outputs from deployed stack ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Outputs ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Key HelloWorldApi Description API Gateway endpoint URL for Prod stage for Hello World function Value https://omw1670rqy.execute-api.amazonaws.com:4566/Prod/hello/ Key HelloWorldFunction Description Hello World Lambda Function ARN Value arn:aws:lambda:us-east-1:000000000000:function:sam-typescript-support-ga-hello-wor-HelloWorldFunction-27e97f91 Key HelloWorldFunctionIamRole Description Implicit IAM Role created for Hello World function Value arn:aws:iam::000000000000:role/sam-typescript-support-ga-hello-HelloWorldFunctionRole-d021ce86 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Successfully created/updated stack - sam-typescript-support-ga-hello-world in us-east-1
ところで、以前のAWS SAMとLocalStackの組み合わせだとsamlocal deploy
では先にAmazon S3バケットを作成しておかないと
いけなかったり、2回目のデプロイがうまくいかなかったりしたのですが、今回はそういう悩みは解消されていました。
確認してみましょう。
$ 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 world"}
OKですね。
テストも確認しておきます。
$ cd hello-world $ 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 (4 ms) ----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------|---------|----------|---------|---------|------------------- All files | 76.66 | 75 | 100 | 76.66 | app.ts | 76.66 | 75 | 100 | 76.66 | 20-26 ----------|---------|----------|---------|---------|------------------- Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.398 s Ran all test suites.
少しカスタマイズする
ここからは、少し内容をカスタマイズしてみましょう。
パッケージを最新化。
$ DEV_PACKAGES=$(cat package.json | jq '.devDependencies | keys | .[]' | perl -wp -e 's!\n! !g; s!"!!g') $ npm uninstall $DEV_PACKAGES $ npm i -D $DEV_PACKAGES
Node.jsの型定義はv18にしておきます。
$ npm uninstall @types/node $ npm i -D @types/node@v18
esbuildも最新化。
$ npm uninstall esbuild $ npm i esbuild
esbuildはdevDependencies
でいいのでは?という気がしないでもないのですが、sam build
時に実行されるnpm install
には
--production
オプションがついているので、devPackages
にesbuildを入れてしまうとsam build
で失敗してしまいます。
Jestはesbuild-jestで動作させるようにしましょう。
$ npm i -D esbuild-jest $ npm uninstall ts-jest
ts-nodeもいいかな、と思います。
$ npm uninstall ts-node
依存関係はこうなりました。
"devDependencies": { "@types/aws-lambda": "^8.10.122", "@types/jest": "^29.5.5", "@types/node": "^18.18.1", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "esbuild-jest": "^0.5.0", "eslint": "^8.50.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "jest": "^29.7.0", "prettier": "^3.0.3", "typescript": "^5.2.2" }, "dependencies": { "esbuild": "^0.19.4" }
Jestの設定ファイルをJavaScriptにして、
$ mv jest.config.ts jest.config.js
でesbuild-jestを使うように変更。
jest.config.js
module.exports = { transform: { '^.+\\.ts?$': 'esbuild-jest', }, clearMocks: true, collectCoverage: true, coverageDirectory: 'coverage', coverageProvider: 'v8', testMatch: ['**/tests/unit/*.test.ts'], };
$ mkdir src $ mv app.ts src
package.json
のscripts
はこんな感じにしました。
"scripts": { "lint": "eslint '{src,tests}/**.{ts,js}' --fix", "typecheck": "tsc", "typecheck:watch": "tsc --watch", "test": "jest" },
tsconfig.json
も変更。
{ "compilerOptions": { "target": "esnext", "strict": true, "noEmit": true, "sourceMap": false, "module":"commonjs", "moduleResolution":"node", "baseUrl": "./src", "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true, "noPropertyAccessFromIndexSignature": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "exclude": ["node_modules", "**/*.test.ts"] }
ソースコードも、変化がわかるように修正。
src/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> => { try { const word = event.queryStringParameters && event.queryStringParameters['word'] ? event.queryStringParameters['word'] : 'TypeScript'; return { statusCode: 200, body: JSON.stringify({ message: `Hello AWS SAM with ${word}`, }), }; } catch (err) { console.log(err); return { statusCode: 500, body: JSON.stringify({ message: 'some error happened', }), }; } };
テストも変更します。
tests/unit/test-handler.test.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { lambdaHandler } from '../../src/app'; import { expect, describe, it } from '@jest/globals'; 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 AWS SAM with TypeScript', }), ); }); });
テストが通ることを確認。
$ npm test
上位ディレクトリに戻って、
$ cd ..
ソースコードの置き場所を変更したので、template.yaml
のEntryPoints
を見直し。
Metadata: # Manage esbuild properties BuildMethod: esbuild BuildProperties: Minify: true Target: "esnext" Sourcemap: true EntryPoints: - src/app.ts
あとはビルドしてデプロイです。
$ samlocal build $ samlocal deploy --region us-east-1
確認。
$ curl http://localhost:4566/restapis/$REST_API_ID/Prod/_user_request_/hello {"message":"Hello AWS SAM with TypeScript"} $ curl http://localhost:4566/restapis/$REST_API_ID/Prod/_user_request_/hello?word=NodeJs {"message":"Hello AWS SAM with NodeJs"}
OKですね。
おわりに
AWS SAMのTypeScriptサポートがいつの間にかGAになっていたので、簡単に確認してみました。
簡単にTypeScriptが使えるのは良いですね。
個人的には、あとはAWS SAMでAmazon API Gatewayを使うAWS Lambda関数を作成すると、AWS SAMがAmazon API Gatewayを
作成してしまうことを避けられない(既存のAmazon API Gatewayを使えない)問題が解決してくれるといいなと思うのですが。
この話はずっと残ったままなので、変わらないんでしょうね。