From 01324a84075eae785fca509db74929525b9b40b7 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Fri, 18 Mar 2022 15:06:05 +0000 Subject: [PATCH 01/25] added example for AWS SAM --- examples/sam/.gitignore | 208 ++++++++++++++++++ examples/sam/README.md | 127 +++++++++++ examples/sam/events/event-get-all-items.json | 3 + examples/sam/events/event-get-by-id.json | 6 + examples/sam/events/event-post-item.json | 4 + examples/sam/hello-world/.eslintignore | 2 + examples/sam/hello-world/.eslintrc.js | 15 ++ examples/sam/hello-world/.npmignore | 1 + examples/sam/hello-world/.prettierrc.js | 7 + examples/sam/hello-world/app.ts | 33 +++ examples/sam/hello-world/jest.config.ts | 15 ++ examples/sam/hello-world/package.json | 33 +++ .../tests/unit/test-handler.test.ts | 64 ++++++ examples/sam/hello-world/tsconfig.json | 15 ++ examples/sam/src/handlers/.eslintignore | 2 + examples/sam/src/handlers/.eslintrc.js | 15 ++ examples/sam/src/handlers/.npmignore | 1 + examples/sam/src/handlers/.prettierrc.js | 7 + examples/sam/src/handlers/get-all-items.ts | 80 +++++++ examples/sam/src/handlers/get-by-id.ts | 83 +++++++ examples/sam/src/handlers/package.json | 26 +++ examples/sam/src/handlers/put-item.ts | 85 +++++++ examples/sam/src/handlers/tsconfig.json | 15 ++ examples/sam/template.yaml | 158 +++++++++++++ 24 files changed, 1005 insertions(+) create mode 100644 examples/sam/.gitignore create mode 100644 examples/sam/README.md create mode 100644 examples/sam/events/event-get-all-items.json create mode 100644 examples/sam/events/event-get-by-id.json create mode 100644 examples/sam/events/event-post-item.json create mode 100644 examples/sam/hello-world/.eslintignore create mode 100644 examples/sam/hello-world/.eslintrc.js create mode 100644 examples/sam/hello-world/.npmignore create mode 100644 examples/sam/hello-world/.prettierrc.js create mode 100644 examples/sam/hello-world/app.ts create mode 100644 examples/sam/hello-world/jest.config.ts create mode 100644 examples/sam/hello-world/package.json create mode 100644 examples/sam/hello-world/tests/unit/test-handler.test.ts create mode 100644 examples/sam/hello-world/tsconfig.json create mode 100644 examples/sam/src/handlers/.eslintignore create mode 100644 examples/sam/src/handlers/.eslintrc.js create mode 100644 examples/sam/src/handlers/.npmignore create mode 100644 examples/sam/src/handlers/.prettierrc.js create mode 100644 examples/sam/src/handlers/get-all-items.ts create mode 100644 examples/sam/src/handlers/get-by-id.ts create mode 100644 examples/sam/src/handlers/package.json create mode 100644 examples/sam/src/handlers/put-item.ts create mode 100644 examples/sam/src/handlers/tsconfig.json create mode 100644 examples/sam/template.yaml diff --git a/examples/sam/.gitignore b/examples/sam/.gitignore new file mode 100644 index 0000000000..41b1156d36 --- /dev/null +++ b/examples/sam/.gitignore @@ -0,0 +1,208 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam diff --git a/examples/sam/README.md b/examples/sam/README.md new file mode 100644 index 0000000000..d024547466 --- /dev/null +++ b/examples/sam/README.md @@ -0,0 +1,127 @@ +# powertools-example + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- hello-world - Code for the application's Lambda function written in TypeScript. +- events - Invocation events that you can use to invoke the function. +- hello-world/tests - Unit tests for the application code. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Node.js - [Install Node.js 14](https://nodejs.org/en/), including the NPM package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +powertools-example$ sam build +``` + +The SAM CLI installs dependencies defined in `hello-world/package.json`, compiles TypeScript with esbuild, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +powertools-example$ sam local invoke HelloWorldFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +powertools-example$ sam local start-api +powertools-example$ curl http://localhost:3000/ +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +powertools-example$ sam logs -n HelloWorldFunction --stack-name powertools-example --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `hello-world/tests` folder in this project. Use NPM to install the [Jest test framework](https://jestjs.io/) and run unit tests. + +```bash +powertools-example$ cd hello-world +hello-world$ npm install +hello-world$ npm run test +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +aws cloudformation delete-stack --stack-name powertools-example +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/examples/sam/events/event-get-all-items.json b/examples/sam/events/event-get-all-items.json new file mode 100644 index 0000000000..3a0cb5f77b --- /dev/null +++ b/examples/sam/events/event-get-all-items.json @@ -0,0 +1,3 @@ +{ + "httpMethod": "GET" +} \ No newline at end of file diff --git a/examples/sam/events/event-get-by-id.json b/examples/sam/events/event-get-by-id.json new file mode 100644 index 0000000000..63a64fb458 --- /dev/null +++ b/examples/sam/events/event-get-by-id.json @@ -0,0 +1,6 @@ +{ + "httpMethod": "GET", + "pathParameters": { + "id": "id1" + } +} \ No newline at end of file diff --git a/examples/sam/events/event-post-item.json b/examples/sam/events/event-post-item.json new file mode 100644 index 0000000000..6367003e54 --- /dev/null +++ b/examples/sam/events/event-post-item.json @@ -0,0 +1,4 @@ +{ + "httpMethod": "POST", + "body": "{\"id\": \"id1\",\"name\": \"name1\"}" +} \ No newline at end of file diff --git a/examples/sam/hello-world/.eslintignore b/examples/sam/hello-world/.eslintignore new file mode 100644 index 0000000000..512d4cb8b6 --- /dev/null +++ b/examples/sam/hello-world/.eslintignore @@ -0,0 +1,2 @@ +node_modules +.aws-sam \ No newline at end of file diff --git a/examples/sam/hello-world/.eslintrc.js b/examples/sam/hello-world/.eslintrc.js new file mode 100644 index 0000000000..5da871fc4f --- /dev/null +++ b/examples/sam/hello-world/.eslintrc.js @@ -0,0 +1,15 @@ +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", + } + }; \ No newline at end of file diff --git a/examples/sam/hello-world/.npmignore b/examples/sam/hello-world/.npmignore new file mode 100644 index 0000000000..e7e1fb04f4 --- /dev/null +++ b/examples/sam/hello-world/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/examples/sam/hello-world/.prettierrc.js b/examples/sam/hello-world/.prettierrc.js new file mode 100644 index 0000000000..4c2c6c78b6 --- /dev/null +++ b/examples/sam/hello-world/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: true, + trailingComma: "all", + singleQuote: true, + printWidth: 120, + tabWidth: 4 + }; \ No newline at end of file diff --git a/examples/sam/hello-world/app.ts b/examples/sam/hello-world/app.ts new file mode 100644 index 0000000000..2cf0f9578b --- /dev/null +++ b/examples/sam/hello-world/app.ts @@ -0,0 +1,33 @@ +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 => { + 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; +}; diff --git a/examples/sam/hello-world/jest.config.ts b/examples/sam/hello-world/jest.config.ts new file mode 100644 index 0000000000..21d6c7c004 --- /dev/null +++ b/examples/sam/hello-world/jest.config.ts @@ -0,0 +1,15 @@ +/* + * 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'], +}; diff --git a/examples/sam/hello-world/package.json b/examples/sam/hello-world/package.json new file mode 100644 index 0000000000..8d7e07f82b --- /dev/null +++ b/examples/sam/hello-world/package.json @@ -0,0 +1,33 @@ +{ + "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" + } +} diff --git a/examples/sam/hello-world/tests/unit/test-handler.test.ts b/examples/sam/hello-world/tests/unit/test-handler.test.ts new file mode 100644 index 0000000000..a3b096bab2 --- /dev/null +++ b/examples/sam/hello-world/tests/unit/test-handler.test.ts @@ -0,0 +1,64 @@ +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', + }), + ); + }); +}); diff --git a/examples/sam/hello-world/tsconfig.json b/examples/sam/hello-world/tsconfig.json new file mode 100644 index 0000000000..ffaf193ea8 --- /dev/null +++ b/examples/sam/hello-world/tsconfig.json @@ -0,0 +1,15 @@ +{ + "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"] + } \ No newline at end of file diff --git a/examples/sam/src/handlers/.eslintignore b/examples/sam/src/handlers/.eslintignore new file mode 100644 index 0000000000..512d4cb8b6 --- /dev/null +++ b/examples/sam/src/handlers/.eslintignore @@ -0,0 +1,2 @@ +node_modules +.aws-sam \ No newline at end of file diff --git a/examples/sam/src/handlers/.eslintrc.js b/examples/sam/src/handlers/.eslintrc.js new file mode 100644 index 0000000000..5da871fc4f --- /dev/null +++ b/examples/sam/src/handlers/.eslintrc.js @@ -0,0 +1,15 @@ +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", + } + }; \ No newline at end of file diff --git a/examples/sam/src/handlers/.npmignore b/examples/sam/src/handlers/.npmignore new file mode 100644 index 0000000000..e7e1fb04f4 --- /dev/null +++ b/examples/sam/src/handlers/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/examples/sam/src/handlers/.prettierrc.js b/examples/sam/src/handlers/.prettierrc.js new file mode 100644 index 0000000000..4c2c6c78b6 --- /dev/null +++ b/examples/sam/src/handlers/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: true, + trailingComma: "all", + singleQuote: true, + printWidth: 120, + tabWidth: 4 + }; \ No newline at end of file diff --git a/examples/sam/src/handlers/get-all-items.ts b/examples/sam/src/handlers/get-all-items.ts new file mode 100644 index 0000000000..ae5c0ba445 --- /dev/null +++ b/examples/sam/src/handlers/get-all-items.ts @@ -0,0 +1,80 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { Metrics } from '@aws-lambda-powertools/metrics'; +import { Logger } from '@aws-lambda-powertools/logger'; +import { Tracer } from '@aws-lambda-powertools/tracer'; +import * as aws_client from 'aws-sdk'; + +// Create the PowerTools clients +const metrics = new Metrics(); +const logger = new Logger(); +const tracer = new Tracer(); + +// Patch aws-sdk for tracing +const AWS = tracer.captureAWS(aws_client); + +// Get the DynamoDB table name from environment variables +const tableName = process.env.SAMPLE_TABLE; + +// Create a DocumentClient that represents the query to add an item +const docClient = new AWS.DynamoDB.DocumentClient(); + +/** + * + * 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 getAllItemsHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { + if (event.httpMethod !== 'GET') { + throw new Error(`getAllItems only accepts GET method, you tried: ${event.httpMethod}`); + } + // Tracer: Get facade segment created by AWS Lambda + const segment = tracer.getSegment(); + + // Tracer: Create subsegment for the function & set it as active + const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); + tracer.setSegment(handlerSegment); + + // Tracer: Annotate the subsegment with the cold start & serviceName + tracer.annotateColdStart(); + tracer.addServiceNameAnnotation(); + + // Tracer: Add annotation for the awsRequestId + tracer.putAnnotation('awsRequestId', context.awsRequestId); + + // Metrics: Capture cold start metrics + metrics.captureColdStartMetric(); + + // All log statements are written to CloudWatch + logger.debug('received:', event); + + // get all items from the table (only first 1MB data, you can use `LastEvaluatedKey` to get the rest of data) + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property + // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html + var params = { + TableName : tableName! + }; + const data = await docClient.scan(params).promise(); + const items = data.Items; + + const response = { + statusCode: 200, + body: JSON.stringify(items) + }; + + // Tracer: Close subsegment (the AWS Lambda one is closed automatically) + handlerSegment.close(); // (## index.handler) + + // Tracer: Set the facade segment as active again (the one created by AWS Lambda) + tracer.setSegment(segment); + + // All log statements are written to CloudWatch + logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); + + return response; +}; diff --git a/examples/sam/src/handlers/get-by-id.ts b/examples/sam/src/handlers/get-by-id.ts new file mode 100644 index 0000000000..afe5fd63fc --- /dev/null +++ b/examples/sam/src/handlers/get-by-id.ts @@ -0,0 +1,83 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { Metrics } from '@aws-lambda-powertools/metrics'; +import { Logger } from '@aws-lambda-powertools/logger'; +import { Tracer } from '@aws-lambda-powertools/tracer'; +import * as aws_client from 'aws-sdk'; + +// Create the PowerTools clients +const metrics = new Metrics(); +const logger = new Logger(); +const tracer = new Tracer(); + +// Patch aws-sdk for tracing +const AWS = tracer.captureAWS(aws_client); + +// Get the DynamoDB table name from environment variables +const tableName = process.env.SAMPLE_TABLE; + +// Create a DocumentClient that represents the query to add an item +const docClient = new AWS.DynamoDB.DocumentClient(); + +/** + * + * 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 getByIdHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { + if (event.httpMethod !== 'GET') { + throw new Error(`getById only accepts GET method, you tried: ${event.httpMethod}`); + } + // Tracer: Get facade segment created by AWS Lambda + const segment = tracer.getSegment(); + + // Tracer: Create subsegment for the function & set it as active + const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); + tracer.setSegment(handlerSegment); + + // Tracer: Annotate the subsegment with the cold start & serviceName + tracer.annotateColdStart(); + tracer.addServiceNameAnnotation(); + + // Tracer: Add annotation for the awsRequestId + tracer.putAnnotation('awsRequestId', context.awsRequestId); + + // Metrics: Capture cold start metrics + metrics.captureColdStartMetric(); + + // All log statements are written to CloudWatch + logger.debug('received:', event); + + // Get id from pathParameters from APIGateway because of `/{id}` at template.yaml + const id = event.pathParameters!.id; + + // Get the item from the table + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property + var params = { + TableName : tableName!, + Key: { id: id }, + }; + const data = await docClient.get(params).promise(); + const item = data.Item; + + const response = { + statusCode: 200, + body: JSON.stringify(item) + }; + + // Tracer: Close subsegment (the AWS Lambda one is closed automatically) + handlerSegment.close(); // (## index.handler) + + // Tracer: Set the facade segment as active again (the one created by AWS Lambda) + tracer.setSegment(segment); + + // All log statements are written to CloudWatch + logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); + + return response; +}; diff --git a/examples/sam/src/handlers/package.json b/examples/sam/src/handlers/package.json new file mode 100644 index 0000000000..a6cfb57a1d --- /dev/null +++ b/examples/sam/src/handlers/package.json @@ -0,0 +1,26 @@ +{ + "name": "hello_world", + "version": "1.0.0", + "description": "AWS Lambda Powertools for TypeScript Sample", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + }, + "scripts": { + "lint": "eslint '*.ts' --quiet --fix", + "compile": "tsc" + }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92", + "@types/node": "^17.0.13", + "@typescript-eslint/eslint-plugin": "^5.10.2", + "@typescript-eslint/parser": "^5.10.2", + "esbuild": "^0.14.14", + "eslint": "^8.8.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", + "prettier": "^2.5.1", + "ts-node": "^10.4.0", + "typescript": "^4.5.5" + } +} diff --git a/examples/sam/src/handlers/put-item.ts b/examples/sam/src/handlers/put-item.ts new file mode 100644 index 0000000000..39d5a4d003 --- /dev/null +++ b/examples/sam/src/handlers/put-item.ts @@ -0,0 +1,85 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { Metrics } from '@aws-lambda-powertools/metrics'; +import { Logger } from '@aws-lambda-powertools/logger'; +import { Tracer } from '@aws-lambda-powertools/tracer'; +import * as aws_client from 'aws-sdk'; + +// Create the PowerTools clients +const metrics = new Metrics(); +const logger = new Logger(); +const tracer = new Tracer(); + +// Patch aws-sdk for tracing +const AWS = tracer.captureAWS(aws_client); + +// Get the DynamoDB table name from environment variables +const tableName = process.env.SAMPLE_TABLE; + +// Create a DocumentClient that represents the query to add an item +const docClient = new AWS.DynamoDB.DocumentClient(); + +/** + * + * 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 putItemHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { + if (event.httpMethod !== 'POST') { + throw new Error(`putItem only accepts POST method, you tried: ${event.httpMethod}`); + } + // Tracer: Get facade segment created by AWS Lambda + const segment = tracer.getSegment(); + + // Tracer: Create subsegment for the function & set it as active + const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); + tracer.setSegment(handlerSegment); + + // Tracer: Annotate the subsegment with the cold start & serviceName + tracer.annotateColdStart(); + tracer.addServiceNameAnnotation(); + + // Tracer: Add annotation for the awsRequestId + tracer.putAnnotation('awsRequestId', context.awsRequestId); + + // Metrics: Capture cold start metrics + metrics.captureColdStartMetric(); + + // All log statements are written to CloudWatch + logger.debug('received:', event); + + // Get id and name from the body of the request + const body = JSON.parse(event.body!); + const id = body.id; + const name = body.name; + + // Creates a new item, or replaces an old item with a new item + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property + var params = { + TableName : tableName!, + Item: { id : id, name: name } + }; + + await docClient.put(params).promise(); + + const response = { + statusCode: 200, + body: JSON.stringify(body) + }; + + // Tracer: Close subsegment (the AWS Lambda one is closed automatically) + handlerSegment.close(); // (## index.handler) + + // Tracer: Set the facade segment as active again (the one created by AWS Lambda) + tracer.setSegment(segment); + + // All log statements are written to CloudWatch + logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); + + return response; +}; diff --git a/examples/sam/src/handlers/tsconfig.json b/examples/sam/src/handlers/tsconfig.json new file mode 100644 index 0000000000..ffaf193ea8 --- /dev/null +++ b/examples/sam/src/handlers/tsconfig.json @@ -0,0 +1,15 @@ +{ + "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"] + } \ No newline at end of file diff --git a/examples/sam/template.yaml b/examples/sam/template.yaml new file mode 100644 index 0000000000..90b8bb9a9e --- /dev/null +++ b/examples/sam/template.yaml @@ -0,0 +1,158 @@ +# This is the SAM template that represents the architecture of your serverless application +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html + +# The AWSTemplateFormatVersion identifies the capabilities of the template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + sam-app + +# Transform section specifies one or more macros that AWS CloudFormation uses to process your template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html +Transform: +- AWS::Serverless-2016-10-31 + +# Resources declares the AWS resources that you want to include in the stack +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html +Resources: + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: get-all-items.js + getAllItemsFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/get-all-items.getAllItemsHandler + Runtime: nodejs14.x + Architectures: + - x86_64 + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP get method to get all items from a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Tracing: Active + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + POWERTOOLS_SERVICE_NAME: getAllItems + POWERTOOLS_METRICS_NAMESPACE: PowertoolsSAMExample + LOG_LEVEL: Debug + Events: + Api: + Type: Api + Properties: + Path: / + Method: GET + Metadata: + # Manage esbuild properties + BuildMethod: esbuild + BuildProperties: + Minify: true + Target: "es2020" + Sourcemap: true, + EntryPoints: + - src/handlers/get-all-items.ts + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: get-by-id.js + getByIdFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/get-by-id.getByIdHandler + Runtime: nodejs14.x + Architectures: + - x86_64 + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP get method to get one item by id from a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Tracing: Active + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + POWERTOOLS_SERVICE_NAME: getById + POWERTOOLS_METRICS_NAMESPACE: PowertoolsSAMExample + LOG_LEVEL: Debug + Events: + Api: + Type: Api + Properties: + Path: /{id} + Method: GET + Metadata: + # Manage esbuild properties + BuildMethod: esbuild + BuildProperties: + Minify: true + Target: "es2020" + Sourcemap: true, + EntryPoints: + - src/handlers/get-by-id.ts + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: put-item.js + putItemFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/put-item.putItemHandler + Runtime: nodejs14.x + Architectures: + - x86_64 + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP post method to add one item to a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Tracing: Active + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + POWERTOOLS_SERVICE_NAME: getById + POWERTOOLS_METRICS_NAMESPACE: PowertoolsSAMExample + LOG_LEVEL: Debug + Events: + Api: + Type: Api + Properties: + Path: / + Method: POST + Metadata: + # Manage esbuild properties + BuildMethod: esbuild + BuildProperties: + Minify: true + Target: "es2020" + Sourcemap: true, + EntryPoints: + - src/handlers/put-item.ts + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # DynamoDB table to store item: {id: <ID>, name: <NAME>} + SampleTable: + Type: AWS::Serverless::SimpleTable + Properties: + PrimaryKey: + Name: id + Type: String + ProvisionedThroughput: + ReadCapacityUnits: 2 + WriteCapacityUnits: 2 + +Outputs: + WebEndpoint: + Description: "API Gateway endpoint URL for Prod stage" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" From 9c3d999ca1da41c775fc97e47fb5763d4d4cea7d Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Tue, 5 Apr 2022 11:30:00 +0000 Subject: [PATCH 02/25] removed unnecessary hello-world folder --- examples/sam/hello-world/.eslintignore | 2 - examples/sam/hello-world/.eslintrc.js | 15 ----- examples/sam/hello-world/.npmignore | 1 - examples/sam/hello-world/.prettierrc.js | 7 -- examples/sam/hello-world/app.ts | 33 ---------- examples/sam/hello-world/jest.config.ts | 15 ----- examples/sam/hello-world/package.json | 33 ---------- .../tests/unit/test-handler.test.ts | 64 ------------------- examples/sam/hello-world/tsconfig.json | 15 ----- 9 files changed, 185 deletions(-) delete mode 100644 examples/sam/hello-world/.eslintignore delete mode 100644 examples/sam/hello-world/.eslintrc.js delete mode 100644 examples/sam/hello-world/.npmignore delete mode 100644 examples/sam/hello-world/.prettierrc.js delete mode 100644 examples/sam/hello-world/app.ts delete mode 100644 examples/sam/hello-world/jest.config.ts delete mode 100644 examples/sam/hello-world/package.json delete mode 100644 examples/sam/hello-world/tests/unit/test-handler.test.ts delete mode 100644 examples/sam/hello-world/tsconfig.json diff --git a/examples/sam/hello-world/.eslintignore b/examples/sam/hello-world/.eslintignore deleted file mode 100644 index 512d4cb8b6..0000000000 --- a/examples/sam/hello-world/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -.aws-sam \ No newline at end of file diff --git a/examples/sam/hello-world/.eslintrc.js b/examples/sam/hello-world/.eslintrc.js deleted file mode 100644 index 5da871fc4f..0000000000 --- a/examples/sam/hello-world/.eslintrc.js +++ /dev/null @@ -1,15 +0,0 @@ -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", - } - }; \ No newline at end of file diff --git a/examples/sam/hello-world/.npmignore b/examples/sam/hello-world/.npmignore deleted file mode 100644 index e7e1fb04f4..0000000000 --- a/examples/sam/hello-world/.npmignore +++ /dev/null @@ -1 +0,0 @@ -tests/* diff --git a/examples/sam/hello-world/.prettierrc.js b/examples/sam/hello-world/.prettierrc.js deleted file mode 100644 index 4c2c6c78b6..0000000000 --- a/examples/sam/hello-world/.prettierrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - semi: true, - trailingComma: "all", - singleQuote: true, - printWidth: 120, - tabWidth: 4 - }; \ No newline at end of file diff --git a/examples/sam/hello-world/app.ts b/examples/sam/hello-world/app.ts deleted file mode 100644 index 2cf0f9578b..0000000000 --- a/examples/sam/hello-world/app.ts +++ /dev/null @@ -1,33 +0,0 @@ -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 => { - 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; -}; diff --git a/examples/sam/hello-world/jest.config.ts b/examples/sam/hello-world/jest.config.ts deleted file mode 100644 index 21d6c7c004..0000000000 --- a/examples/sam/hello-world/jest.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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'], -}; diff --git a/examples/sam/hello-world/package.json b/examples/sam/hello-world/package.json deleted file mode 100644 index 8d7e07f82b..0000000000 --- a/examples/sam/hello-world/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "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" - } -} diff --git a/examples/sam/hello-world/tests/unit/test-handler.test.ts b/examples/sam/hello-world/tests/unit/test-handler.test.ts deleted file mode 100644 index a3b096bab2..0000000000 --- a/examples/sam/hello-world/tests/unit/test-handler.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -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', - }), - ); - }); -}); diff --git a/examples/sam/hello-world/tsconfig.json b/examples/sam/hello-world/tsconfig.json deleted file mode 100644 index ffaf193ea8..0000000000 --- a/examples/sam/hello-world/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "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"] - } \ No newline at end of file From ed0abd2f3022b1d0f98c868d59c1c03483978bf2 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Tue, 5 Apr 2022 11:35:24 +0000 Subject: [PATCH 03/25] tsconfig now in line with other tsconfigs in this repo --- examples/sam/src/handlers/tsconfig.json | 39 ++++++++++++++++--------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/examples/sam/src/handlers/tsconfig.json b/examples/sam/src/handlers/tsconfig.json index ffaf193ea8..6fe6024ff3 100644 --- a/examples/sam/src/handlers/tsconfig.json +++ b/examples/sam/src/handlers/tsconfig.json @@ -1,15 +1,28 @@ { - "compilerOptions": { - "target": "es2020", + "compilerOptions": { + "experimentalDecorators": true, + "noImplicitAny": true, + "target": "ES2020", + "module": "commonjs", + "declaration": true, + "declarationMap": true, + "outDir": "lib", + "removeComments": false, "strict": true, - "preserveConstEnums": true, - "noEmit": true, - "sourceMap": false, - "module":"es2015", - "moduleResolution":"node", - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - }, - "exclude": ["node_modules", "**/*.test.ts"] - } \ No newline at end of file + "inlineSourceMap": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "pretty": true, + "esModuleInterop": true + }, + "exclude": [ "./node_modules"], + "watchOptions": { + "watchFile": "useFsEvents", + "watchDirectory": "useFsEvents", + "fallbackPolling": "dynamicPriority" + }, + "lib": [ "es2020" ], + "types": [ + "node" + ] +} \ No newline at end of file From fd6acbc63b064b97b8db960e93ea3716dfe49bb8 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Tue, 5 Apr 2022 12:11:25 +0000 Subject: [PATCH 04/25] added try/catch block + metric annotation + error logging for dynamodb operations --- examples/sam/src/handlers/get-all-items.ts | 25 +++++++++++++------ examples/sam/src/handlers/get-by-id.ts | 29 +++++++++++++++------- examples/sam/src/handlers/put-item.ts | 26 +++++++++++++------ 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/examples/sam/src/handlers/get-all-items.ts b/examples/sam/src/handlers/get-all-items.ts index ae5c0ba445..884b506c19 100644 --- a/examples/sam/src/handlers/get-all-items.ts +++ b/examples/sam/src/handlers/get-all-items.ts @@ -57,15 +57,26 @@ export const getAllItemsHandler = async (event: APIGatewayProxyEvent, context: C // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html var params = { - TableName : tableName! + TableName: tableName! }; - const data = await docClient.scan(params).promise(); - const items = data.Items; - const response = { - statusCode: 200, - body: JSON.stringify(items) - }; + var response; + + try { + const data = await docClient.scan(params).promise(); + const items = data.Items; + response = { + statusCode: 200, + body: JSON.stringify(items) + }; + } catch (err) { + tracer.addErrorAsMetadata(err as Error); + logger.error("Error reading from table. " + err) + response = { + statusCode: 500, + body: JSON.stringify({ "error": "Error reading from table." }) + }; + } // Tracer: Close subsegment (the AWS Lambda one is closed automatically) handlerSegment.close(); // (## index.handler) diff --git a/examples/sam/src/handlers/get-by-id.ts b/examples/sam/src/handlers/get-by-id.ts index afe5fd63fc..4c397112ed 100644 --- a/examples/sam/src/handlers/get-by-id.ts +++ b/examples/sam/src/handlers/get-by-id.ts @@ -55,20 +55,31 @@ export const getByIdHandler = async (event: APIGatewayProxyEvent, context: Conte // Get id from pathParameters from APIGateway because of `/{id}` at template.yaml const id = event.pathParameters!.id; - + // Get the item from the table // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property var params = { - TableName : tableName!, + TableName: tableName!, Key: { id: id }, }; - const data = await docClient.get(params).promise(); - const item = data.Item; - - const response = { - statusCode: 200, - body: JSON.stringify(item) - }; + + var response; + + try { + const data = await docClient.get(params).promise(); + const item = data.Item; + response = { + statusCode: 200, + body: JSON.stringify(item) + }; + } catch (err) { + tracer.addErrorAsMetadata(err as Error); + logger.error("Error reading from table. " + err) + response = { + statusCode: 500, + body: JSON.stringify({ "error": "Error reading from table." }) + }; + } // Tracer: Close subsegment (the AWS Lambda one is closed automatically) handlerSegment.close(); // (## index.handler) diff --git a/examples/sam/src/handlers/put-item.ts b/examples/sam/src/handlers/put-item.ts index 39d5a4d003..0926d900ff 100644 --- a/examples/sam/src/handlers/put-item.ts +++ b/examples/sam/src/handlers/put-item.ts @@ -61,16 +61,26 @@ export const putItemHandler = async (event: APIGatewayProxyEvent, context: Conte // Creates a new item, or replaces an old item with a new item // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property var params = { - TableName : tableName!, - Item: { id : id, name: name } + TableName: tableName!, + Item: { id: id, name: name } }; - await docClient.put(params).promise(); - - const response = { - statusCode: 200, - body: JSON.stringify(body) - }; + var response; + + try { + await docClient.put(params).promise(); + response = { + statusCode: 200, + body: JSON.stringify(body) + }; + } catch (err) { + tracer.addErrorAsMetadata(err as Error); + logger.error("Error writing data to table. " + err) + response = { + statusCode: 500, + body: JSON.stringify({ "error": "Error writing data to table." }) + }; + } // Tracer: Close subsegment (the AWS Lambda one is closed automatically) handlerSegment.close(); // (## index.handler) From 644a76ac13c2d2e30b82a93bc73c05d514403653 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Tue, 5 Apr 2022 12:19:30 +0000 Subject: [PATCH 05/25] Importing only DynamoDB instead of whole aws-sdk. Patching only the DynamoDB DocumentClient --- examples/sam/src/handlers/get-all-items.ts | 9 +++------ examples/sam/src/handlers/get-by-id.ts | 9 +++------ examples/sam/src/handlers/put-item.ts | 9 +++------ 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/examples/sam/src/handlers/get-all-items.ts b/examples/sam/src/handlers/get-all-items.ts index 884b506c19..1a7fc4a856 100644 --- a/examples/sam/src/handlers/get-all-items.ts +++ b/examples/sam/src/handlers/get-all-items.ts @@ -2,22 +2,19 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda import { Metrics } from '@aws-lambda-powertools/metrics'; import { Logger } from '@aws-lambda-powertools/logger'; import { Tracer } from '@aws-lambda-powertools/tracer'; -import * as aws_client from 'aws-sdk'; +import { DynamoDB } from 'aws-sdk'; // Create the PowerTools clients const metrics = new Metrics(); const logger = new Logger(); const tracer = new Tracer(); -// Patch aws-sdk for tracing -const AWS = tracer.captureAWS(aws_client); +// Create DynamoDB DocumentClient and patch it for tracing +const docClient = tracer.captureAWS(new DynamoDB.DocumentClient()); // Get the DynamoDB table name from environment variables const tableName = process.env.SAMPLE_TABLE; -// Create a DocumentClient that represents the query to add an item -const docClient = new AWS.DynamoDB.DocumentClient(); - /** * * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format diff --git a/examples/sam/src/handlers/get-by-id.ts b/examples/sam/src/handlers/get-by-id.ts index 4c397112ed..b1d84e8f67 100644 --- a/examples/sam/src/handlers/get-by-id.ts +++ b/examples/sam/src/handlers/get-by-id.ts @@ -2,22 +2,19 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda import { Metrics } from '@aws-lambda-powertools/metrics'; import { Logger } from '@aws-lambda-powertools/logger'; import { Tracer } from '@aws-lambda-powertools/tracer'; -import * as aws_client from 'aws-sdk'; +import { DynamoDB } from 'aws-sdk'; // Create the PowerTools clients const metrics = new Metrics(); const logger = new Logger(); const tracer = new Tracer(); -// Patch aws-sdk for tracing -const AWS = tracer.captureAWS(aws_client); +// Create DynamoDB DocumentClient and patch it for tracing +const docClient = tracer.captureAWS(new DynamoDB.DocumentClient()); // Get the DynamoDB table name from environment variables const tableName = process.env.SAMPLE_TABLE; -// Create a DocumentClient that represents the query to add an item -const docClient = new AWS.DynamoDB.DocumentClient(); - /** * * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format diff --git a/examples/sam/src/handlers/put-item.ts b/examples/sam/src/handlers/put-item.ts index 0926d900ff..c335352c6e 100644 --- a/examples/sam/src/handlers/put-item.ts +++ b/examples/sam/src/handlers/put-item.ts @@ -2,22 +2,19 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda import { Metrics } from '@aws-lambda-powertools/metrics'; import { Logger } from '@aws-lambda-powertools/logger'; import { Tracer } from '@aws-lambda-powertools/tracer'; -import * as aws_client from 'aws-sdk'; +import { DynamoDB } from 'aws-sdk'; // Create the PowerTools clients const metrics = new Metrics(); const logger = new Logger(); const tracer = new Tracer(); -// Patch aws-sdk for tracing -const AWS = tracer.captureAWS(aws_client); +// Create DynamoDB DocumentClient and patch it for tracing +const docClient = tracer.captureAWS(new DynamoDB.DocumentClient()); // Get the DynamoDB table name from environment variables const tableName = process.env.SAMPLE_TABLE; -// Create a DocumentClient that represents the query to add an item -const docClient = new AWS.DynamoDB.DocumentClient(); - /** * * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format From 58a3ca4bb4404e0ec142017fdd7c50187849d2f0 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Tue, 5 Apr 2022 12:29:15 +0000 Subject: [PATCH 06/25] removed prettier; aligned dependency versions with others in this repo --- examples/sam/src/handlers/package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/sam/src/handlers/package.json b/examples/sam/src/handlers/package.json index a6cfb57a1d..9555c8b94d 100644 --- a/examples/sam/src/handlers/package.json +++ b/examples/sam/src/handlers/package.json @@ -5,22 +5,22 @@ "author": "SAM CLI", "license": "MIT", "dependencies": { + "@aws-lambda-powertools/logger": "^0.7.0", + "@aws-lambda-powertools/metrics": "^0.7.0", + "@aws-lambda-powertools/tracer": "^0.7.0" }, "scripts": { "lint": "eslint '*.ts' --quiet --fix", "compile": "tsc" }, "devDependencies": { - "@types/aws-lambda": "^8.10.92", - "@types/node": "^17.0.13", - "@typescript-eslint/eslint-plugin": "^5.10.2", - "@typescript-eslint/parser": "^5.10.2", - "esbuild": "^0.14.14", - "eslint": "^8.8.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-prettier": "^4.0.0", - "prettier": "^2.5.1", - "ts-node": "^10.4.0", - "typescript": "^4.5.5" + "@types/aws-lambda": "^8.10.86", + "@types/node": "17.0.10", + "@typescript-eslint/eslint-plugin": "^5.12.1", + "@typescript-eslint/parser": "^5.12.1", + "esbuild": "^0.14.23", + "eslint": "^8.4.0", + "ts-node": "^10.0.0", + "typescript": "^4.1.3" } } From ef8239a736af94b001510c62c56b7306de5cca6d Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels <33482891+bpauwels@users.noreply.github.com> Date: Tue, 12 Apr 2022 10:39:42 +0200 Subject: [PATCH 07/25] Update examples/sam/README.md Co-authored-by: Sara Gerion <47529391+saragerion@users.noreply.github.com> --- examples/sam/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sam/README.md b/examples/sam/README.md index d024547466..94936decb0 100644 --- a/examples/sam/README.md +++ b/examples/sam/README.md @@ -1,4 +1,4 @@ -# powertools-example +# AWS Lambda Powertools for TypeScript examples in SAM This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. From cf2f9c6491b57d19e7672d9a2a8b4f030b81b74c Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels <33482891+bpauwels@users.noreply.github.com> Date: Tue, 12 Apr 2022 10:40:31 +0200 Subject: [PATCH 08/25] Update examples/sam/README.md Co-authored-by: Sara Gerion <47529391+saragerion@users.noreply.github.com> --- examples/sam/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sam/README.md b/examples/sam/README.md index 94936decb0..8917652b03 100644 --- a/examples/sam/README.md +++ b/examples/sam/README.md @@ -56,7 +56,7 @@ You can find your API Gateway Endpoint URL in the output values displayed after Build your application with the `sam build` command. ```bash -powertools-example$ sam build +aws-lambda-powertools-typescript/examples/sam$ sam build ``` The SAM CLI installs dependencies defined in `hello-world/package.json`, compiles TypeScript with esbuild, creates a deployment package, and saves it in the `.aws-sam/build` folder. From cb28daf93c76383881f0320cb42f04124416976e Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Tue, 12 Apr 2022 08:50:00 +0000 Subject: [PATCH 09/25] seperated lambda function code --- .../get-all-items.ts | 0 .../handlers => lambda-functions}/get-by-id.ts | 0 .../handlers => lambda-functions}/package.json | 0 .../handlers => lambda-functions}/put-item.ts | 0 .../tsconfig.json | 0 examples/sam/README.md | 18 ++++++++---------- examples/sam/src/handlers/.eslintignore | 2 -- examples/sam/src/handlers/.eslintrc.js | 15 --------------- examples/sam/src/handlers/.npmignore | 1 - examples/sam/src/handlers/.prettierrc.js | 7 ------- .../src/handlers/COPY_LAMBDA_FUNCTIONS_HERE | 0 11 files changed, 8 insertions(+), 35 deletions(-) rename examples/{sam/src/handlers => lambda-functions}/get-all-items.ts (100%) rename examples/{sam/src/handlers => lambda-functions}/get-by-id.ts (100%) rename examples/{sam/src/handlers => lambda-functions}/package.json (100%) rename examples/{sam/src/handlers => lambda-functions}/put-item.ts (100%) rename examples/{sam/src/handlers => lambda-functions}/tsconfig.json (100%) delete mode 100644 examples/sam/src/handlers/.eslintignore delete mode 100644 examples/sam/src/handlers/.eslintrc.js delete mode 100644 examples/sam/src/handlers/.npmignore delete mode 100644 examples/sam/src/handlers/.prettierrc.js create mode 100644 examples/sam/src/handlers/COPY_LAMBDA_FUNCTIONS_HERE diff --git a/examples/sam/src/handlers/get-all-items.ts b/examples/lambda-functions/get-all-items.ts similarity index 100% rename from examples/sam/src/handlers/get-all-items.ts rename to examples/lambda-functions/get-all-items.ts diff --git a/examples/sam/src/handlers/get-by-id.ts b/examples/lambda-functions/get-by-id.ts similarity index 100% rename from examples/sam/src/handlers/get-by-id.ts rename to examples/lambda-functions/get-by-id.ts diff --git a/examples/sam/src/handlers/package.json b/examples/lambda-functions/package.json similarity index 100% rename from examples/sam/src/handlers/package.json rename to examples/lambda-functions/package.json diff --git a/examples/sam/src/handlers/put-item.ts b/examples/lambda-functions/put-item.ts similarity index 100% rename from examples/sam/src/handlers/put-item.ts rename to examples/lambda-functions/put-item.ts diff --git a/examples/sam/src/handlers/tsconfig.json b/examples/lambda-functions/tsconfig.json similarity index 100% rename from examples/sam/src/handlers/tsconfig.json rename to examples/lambda-functions/tsconfig.json diff --git a/examples/sam/README.md b/examples/sam/README.md index 8917652b03..6e9d7f4bab 100644 --- a/examples/sam/README.md +++ b/examples/sam/README.md @@ -4,7 +4,6 @@ This project contains source code and supporting files for a serverless applicat - hello-world - Code for the application's Lambda function written in TypeScript. - events - Invocation events that you can use to invoke the function. -- hello-world/tests - Unit tests for the application code. - template.yaml - A template that defines the application's AWS resources. The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. @@ -24,6 +23,14 @@ The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI * [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) * [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) +## Prepare the project + +Before deploying this example, copy the Lambda Function Code to `src/handlers` + +```bash +cp -R ../lambda-functions/* ./src/handlers +``` + ## Deploy the sample application The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. @@ -102,15 +109,6 @@ powertools-example$ sam logs -n HelloWorldFunction --stack-name powertools-examp You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). -## Unit tests - -Tests are defined in the `hello-world/tests` folder in this project. Use NPM to install the [Jest test framework](https://jestjs.io/) and run unit tests. - -```bash -powertools-example$ cd hello-world -hello-world$ npm install -hello-world$ npm run test -``` ## Cleanup diff --git a/examples/sam/src/handlers/.eslintignore b/examples/sam/src/handlers/.eslintignore deleted file mode 100644 index 512d4cb8b6..0000000000 --- a/examples/sam/src/handlers/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -.aws-sam \ No newline at end of file diff --git a/examples/sam/src/handlers/.eslintrc.js b/examples/sam/src/handlers/.eslintrc.js deleted file mode 100644 index 5da871fc4f..0000000000 --- a/examples/sam/src/handlers/.eslintrc.js +++ /dev/null @@ -1,15 +0,0 @@ -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", - } - }; \ No newline at end of file diff --git a/examples/sam/src/handlers/.npmignore b/examples/sam/src/handlers/.npmignore deleted file mode 100644 index e7e1fb04f4..0000000000 --- a/examples/sam/src/handlers/.npmignore +++ /dev/null @@ -1 +0,0 @@ -tests/* diff --git a/examples/sam/src/handlers/.prettierrc.js b/examples/sam/src/handlers/.prettierrc.js deleted file mode 100644 index 4c2c6c78b6..0000000000 --- a/examples/sam/src/handlers/.prettierrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - semi: true, - trailingComma: "all", - singleQuote: true, - printWidth: 120, - tabWidth: 4 - }; \ No newline at end of file diff --git a/examples/sam/src/handlers/COPY_LAMBDA_FUNCTIONS_HERE b/examples/sam/src/handlers/COPY_LAMBDA_FUNCTIONS_HERE new file mode 100644 index 0000000000..e69de29bb2 From 5266452bb2f417c97cad700d3252a60abbc62ac9 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 20 Apr 2022 13:38:54 +0000 Subject: [PATCH 10/25] Updated README.md --- examples/sam/README.md | 45 ++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/examples/sam/README.md b/examples/sam/README.md index 6e9d7f4bab..002651f32b 100644 --- a/examples/sam/README.md +++ b/examples/sam/README.md @@ -1,10 +1,12 @@ # AWS Lambda Powertools for TypeScript examples in SAM -This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. +This project contains source code and supporting files for a serverless application that you can deploy with the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. -- hello-world - Code for the application's Lambda function written in TypeScript. -- events - Invocation events that you can use to invoke the function. -- template.yaml - A template that defines the application's AWS resources. +This project includes the following files and folders: + +- `src/handlers` - Code for the application's Lambda function written in TypeScript. +- `events` - Invocation events that you can use to invoke the function. +- `template.yaml` - A template that defines the application's AWS resources. The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. @@ -23,32 +25,36 @@ The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI * [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) * [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) +You will need to have a valid AWS Account in order to deploy these resources. These resources may incur costs to your AWS Account. The cost from **some services** are covered by the [AWS Free Tier](https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all) but not all of them. If you don't have an AWS Account follow [these instructions to create one](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/). + ## Prepare the project -Before deploying this example, copy the Lambda Function Code to `src/handlers` +All following commands must be executed inside the folder `examples/sam` + +Before deploying this example, copy the Lambda Function Code to `src/handlers`, ```bash cp -R ../lambda-functions/* ./src/handlers ``` -## Deploy the sample application +and install the npm dependencies -The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. +```bash +npm i +``` -To use the SAM CLI, you need the following tools. +In addition to the [recommended setup for this project](https://github.com/awslabs/aws-lambda-powertools-typescript/blob/main/CONTRIBUTING.md#setup), you'll need the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). -* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) -* Node.js - [Install Node.js 14](https://nodejs.org/en/), including the NPM package management tool. -* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) +## Deploy the sample application To build and deploy your application for the first time, run the following in your shell: ```bash -sam build +sam build --beta-features sam deploy --guided ``` -The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: +The first command will build the source of your application. Using esbuild for bundling Node.js and TypeScript is a beta feature, therefore we add the `--beta-features` parameter. The second command will package and deploy your application to AWS, with a series of prompts: * **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. * **AWS Region**: The AWS region you want to deploy your app to. @@ -63,17 +69,17 @@ You can find your API Gateway Endpoint URL in the output values displayed after Build your application with the `sam build` command. ```bash -aws-lambda-powertools-typescript/examples/sam$ sam build +sam build --beta-features ``` -The SAM CLI installs dependencies defined in `hello-world/package.json`, compiles TypeScript with esbuild, creates a deployment package, and saves it in the `.aws-sam/build` folder. +The SAM CLI installs dependencies defined in `src/handlers/package.json`, compiles TypeScript with esbuild, creates a deployment package, and saves it in the `.aws-sam/build` folder. Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. Run functions locally and invoke them with the `sam local invoke` command. ```bash -powertools-example$ sam local invoke HelloWorldFunction --event events/event.json +sam local invoke getAllItemsFunction --event events/event-get-all-items.json ``` The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. @@ -90,13 +96,10 @@ The SAM CLI reads the application template to determine the API's routes and the HelloWorld: Type: Api Properties: - Path: /hello + Path: / Method: get ``` -## Add a resource to your application -The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. - ## Fetch, tail, and filter Lambda function logs To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. @@ -104,7 +107,7 @@ To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` `NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. ```bash -powertools-example$ sam logs -n HelloWorldFunction --stack-name powertools-example --tail +sam logs -n getAllItemsFunction --stack-name powertools-example --tail ``` You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). From 007c990dbf886406e4b7220b1b1eca9f108d70fd Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 20 Apr 2022 13:45:00 +0000 Subject: [PATCH 11/25] Updated README.md removed powertools example folder --- examples/sam/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/sam/README.md b/examples/sam/README.md index 002651f32b..4caee47d1b 100644 --- a/examples/sam/README.md +++ b/examples/sam/README.md @@ -85,8 +85,8 @@ sam local invoke getAllItemsFunction --event events/event-get-all-items.json The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. ```bash -powertools-example$ sam local start-api -powertools-example$ curl http://localhost:3000/ +sam local start-api +curl http://localhost:3000/ ``` The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. From f6bc21ccc453073e08d4e6c78978549675fb73f4 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 20 Apr 2022 13:49:17 +0000 Subject: [PATCH 12/25] Updated README.md changed cleanup to sam delete --- examples/sam/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/sam/README.md b/examples/sam/README.md index 4caee47d1b..600b5ed9d6 100644 --- a/examples/sam/README.md +++ b/examples/sam/README.md @@ -115,10 +115,10 @@ You can find more information and examples about filtering Lambda function logs ## Cleanup -To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: +To delete the sample application that you created, run ```bash -aws cloudformation delete-stack --stack-name powertools-example +sam delete ``` ## Resources From caf39e2bad3fa8a8fc6b33044a63db1b0bed056a Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 20 Apr 2022 14:06:28 +0000 Subject: [PATCH 13/25] fixed eslint issues --- examples/lambda-functions/get-all-items.ts | 113 ++++++++++--------- examples/lambda-functions/get-by-id.ts | 115 ++++++++++---------- examples/lambda-functions/package.json | 1 - examples/lambda-functions/put-item.ts | 121 ++++++++++----------- 4 files changed, 173 insertions(+), 177 deletions(-) diff --git a/examples/lambda-functions/get-all-items.ts b/examples/lambda-functions/get-all-items.ts index 1a7fc4a856..c1e7ff37c1 100644 --- a/examples/lambda-functions/get-all-items.ts +++ b/examples/lambda-functions/get-all-items.ts @@ -25,64 +25,63 @@ const tableName = process.env.SAMPLE_TABLE; * */ - export const getAllItemsHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { - if (event.httpMethod !== 'GET') { - throw new Error(`getAllItems only accepts GET method, you tried: ${event.httpMethod}`); - } - // Tracer: Get facade segment created by AWS Lambda - const segment = tracer.getSegment(); - - // Tracer: Create subsegment for the function & set it as active - const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); - tracer.setSegment(handlerSegment); - - // Tracer: Annotate the subsegment with the cold start & serviceName - tracer.annotateColdStart(); - tracer.addServiceNameAnnotation(); - - // Tracer: Add annotation for the awsRequestId - tracer.putAnnotation('awsRequestId', context.awsRequestId); - - // Metrics: Capture cold start metrics - metrics.captureColdStartMetric(); - - // All log statements are written to CloudWatch - logger.debug('received:', event); - - // get all items from the table (only first 1MB data, you can use `LastEvaluatedKey` to get the rest of data) - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property - // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html - var params = { - TableName: tableName! + if (event.httpMethod !== 'GET') { + throw new Error(`getAllItems only accepts GET method, you tried: ${event.httpMethod}`); + } + // Tracer: Get facade segment created by AWS Lambda + const segment = tracer.getSegment(); + + // Tracer: Create subsegment for the function & set it as active + const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); + tracer.setSegment(handlerSegment); + + // Tracer: Annotate the subsegment with the cold start & serviceName + tracer.annotateColdStart(); + tracer.addServiceNameAnnotation(); + + // Tracer: Add annotation for the awsRequestId + tracer.putAnnotation('awsRequestId', context.awsRequestId); + + // Metrics: Capture cold start metrics + metrics.captureColdStartMetric(); + + // All log statements are written to CloudWatch + logger.debug('received:', event); + + // get all items from the table (only first 1MB data, you can use `LastEvaluatedKey` to get the rest of data) + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property + // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html + const params = { + TableName: tableName! + }; + + let response; + + try { + const data = await docClient.scan(params).promise(); + const items = data.Items; + response = { + statusCode: 200, + body: JSON.stringify(items) + }; + } catch (err) { + tracer.addErrorAsMetadata(err as Error); + logger.error('Error reading from table. ' + err); + response = { + statusCode: 500, + body: JSON.stringify({ 'error': 'Error reading from table.' }) }; + } + + // Tracer: Close subsegment (the AWS Lambda one is closed automatically) + handlerSegment.close(); // (## index.handler) + + // Tracer: Set the facade segment as active again (the one created by AWS Lambda) + tracer.setSegment(segment); + + // All log statements are written to CloudWatch + logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); - var response; - - try { - const data = await docClient.scan(params).promise(); - const items = data.Items; - response = { - statusCode: 200, - body: JSON.stringify(items) - }; - } catch (err) { - tracer.addErrorAsMetadata(err as Error); - logger.error("Error reading from table. " + err) - response = { - statusCode: 500, - body: JSON.stringify({ "error": "Error reading from table." }) - }; - } - - // Tracer: Close subsegment (the AWS Lambda one is closed automatically) - handlerSegment.close(); // (## index.handler) - - // Tracer: Set the facade segment as active again (the one created by AWS Lambda) - tracer.setSegment(segment); - - // All log statements are written to CloudWatch - logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); - - return response; + return response; }; diff --git a/examples/lambda-functions/get-by-id.ts b/examples/lambda-functions/get-by-id.ts index b1d84e8f67..2461efb5fa 100644 --- a/examples/lambda-functions/get-by-id.ts +++ b/examples/lambda-functions/get-by-id.ts @@ -25,67 +25,66 @@ const tableName = process.env.SAMPLE_TABLE; * */ - export const getByIdHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { - if (event.httpMethod !== 'GET') { - throw new Error(`getById only accepts GET method, you tried: ${event.httpMethod}`); - } - // Tracer: Get facade segment created by AWS Lambda - const segment = tracer.getSegment(); - - // Tracer: Create subsegment for the function & set it as active - const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); - tracer.setSegment(handlerSegment); - - // Tracer: Annotate the subsegment with the cold start & serviceName - tracer.annotateColdStart(); - tracer.addServiceNameAnnotation(); - - // Tracer: Add annotation for the awsRequestId - tracer.putAnnotation('awsRequestId', context.awsRequestId); - - // Metrics: Capture cold start metrics - metrics.captureColdStartMetric(); + if (event.httpMethod !== 'GET') { + throw new Error(`getById only accepts GET method, you tried: ${event.httpMethod}`); + } + // Tracer: Get facade segment created by AWS Lambda + const segment = tracer.getSegment(); + + // Tracer: Create subsegment for the function & set it as active + const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); + tracer.setSegment(handlerSegment); + + // Tracer: Annotate the subsegment with the cold start & serviceName + tracer.annotateColdStart(); + tracer.addServiceNameAnnotation(); + + // Tracer: Add annotation for the awsRequestId + tracer.putAnnotation('awsRequestId', context.awsRequestId); + + // Metrics: Capture cold start metrics + metrics.captureColdStartMetric(); + + // All log statements are written to CloudWatch + logger.debug('received:', event); + + // Get id from pathParameters from APIGateway because of `/{id}` at template.yaml + const id = event.pathParameters!.id; + + // Get the item from the table + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property + const params = { + TableName: tableName!, + Key: { id: id }, + }; + + let response; + + try { + const data = await docClient.get(params).promise(); + const item = data.Item; + response = { + statusCode: 200, + body: JSON.stringify(item) + }; + } catch (err) { + tracer.addErrorAsMetadata(err as Error); + logger.error('Error reading from table. ' + err); + response = { + statusCode: 500, + body: JSON.stringify({ 'error': 'Error reading from table.' }) + }; + } - // All log statements are written to CloudWatch - logger.debug('received:', event); + // Tracer: Close subsegment (the AWS Lambda one is closed automatically) + handlerSegment.close(); // (## index.handler) - // Get id from pathParameters from APIGateway because of `/{id}` at template.yaml - const id = event.pathParameters!.id; + // Tracer: Set the facade segment as active again (the one created by AWS Lambda) + tracer.setSegment(segment); - // Get the item from the table - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property - var params = { - TableName: tableName!, - Key: { id: id }, - }; + // All log statements are written to CloudWatch + logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); - var response; - - try { - const data = await docClient.get(params).promise(); - const item = data.Item; - response = { - statusCode: 200, - body: JSON.stringify(item) - }; - } catch (err) { - tracer.addErrorAsMetadata(err as Error); - logger.error("Error reading from table. " + err) - response = { - statusCode: 500, - body: JSON.stringify({ "error": "Error reading from table." }) - }; - } - - // Tracer: Close subsegment (the AWS Lambda one is closed automatically) - handlerSegment.close(); // (## index.handler) - - // Tracer: Set the facade segment as active again (the one created by AWS Lambda) - tracer.setSegment(segment); - - // All log statements are written to CloudWatch - logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); - - return response; + return response; }; diff --git a/examples/lambda-functions/package.json b/examples/lambda-functions/package.json index 9555c8b94d..ce999ad818 100644 --- a/examples/lambda-functions/package.json +++ b/examples/lambda-functions/package.json @@ -16,7 +16,6 @@ "devDependencies": { "@types/aws-lambda": "^8.10.86", "@types/node": "17.0.10", - "@typescript-eslint/eslint-plugin": "^5.12.1", "@typescript-eslint/parser": "^5.12.1", "esbuild": "^0.14.23", "eslint": "^8.4.0", diff --git a/examples/lambda-functions/put-item.ts b/examples/lambda-functions/put-item.ts index c335352c6e..b946c5c10e 100644 --- a/examples/lambda-functions/put-item.ts +++ b/examples/lambda-functions/put-item.ts @@ -25,68 +25,67 @@ const tableName = process.env.SAMPLE_TABLE; * */ - export const putItemHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { - if (event.httpMethod !== 'POST') { - throw new Error(`putItem only accepts POST method, you tried: ${event.httpMethod}`); - } - // Tracer: Get facade segment created by AWS Lambda - const segment = tracer.getSegment(); - - // Tracer: Create subsegment for the function & set it as active - const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); - tracer.setSegment(handlerSegment); - - // Tracer: Annotate the subsegment with the cold start & serviceName - tracer.annotateColdStart(); - tracer.addServiceNameAnnotation(); - - // Tracer: Add annotation for the awsRequestId - tracer.putAnnotation('awsRequestId', context.awsRequestId); - - // Metrics: Capture cold start metrics - metrics.captureColdStartMetric(); - - // All log statements are written to CloudWatch - logger.debug('received:', event); - - // Get id and name from the body of the request - const body = JSON.parse(event.body!); - const id = body.id; - const name = body.name; - - // Creates a new item, or replaces an old item with a new item - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property - var params = { - TableName: tableName!, - Item: { id: id, name: name } + if (event.httpMethod !== 'POST') { + throw new Error(`putItem only accepts POST method, you tried: ${event.httpMethod}`); + } + // Tracer: Get facade segment created by AWS Lambda + const segment = tracer.getSegment(); + + // Tracer: Create subsegment for the function & set it as active + const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); + tracer.setSegment(handlerSegment); + + // Tracer: Annotate the subsegment with the cold start & serviceName + tracer.annotateColdStart(); + tracer.addServiceNameAnnotation(); + + // Tracer: Add annotation for the awsRequestId + tracer.putAnnotation('awsRequestId', context.awsRequestId); + + // Metrics: Capture cold start metrics + metrics.captureColdStartMetric(); + + // All log statements are written to CloudWatch + logger.debug('received:', event); + + // Get id and name from the body of the request + const body = JSON.parse(event.body!); + const id = body.id; + const name = body.name; + + // Creates a new item, or replaces an old item with a new item + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property + const params = { + TableName: tableName!, + Item: { id: id, name: name } + }; + + let response; + + try { + await docClient.put(params).promise(); + response = { + statusCode: 200, + body: JSON.stringify(body) + }; + } catch (err) { + tracer.addErrorAsMetadata(err as Error); + logger.error('Error writing data to table. ' + err); + response = { + statusCode: 500, + body: JSON.stringify({ 'error': 'Error writing data to table.' }) }; + } + + // Tracer: Close subsegment (the AWS Lambda one is closed automatically) + handlerSegment.close(); // (## index.handler) + + // Tracer: Set the facade segment as active again (the one created by AWS Lambda) + tracer.setSegment(segment); + + // All log statements are written to CloudWatch + logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); - var response; - - try { - await docClient.put(params).promise(); - response = { - statusCode: 200, - body: JSON.stringify(body) - }; - } catch (err) { - tracer.addErrorAsMetadata(err as Error); - logger.error("Error writing data to table. " + err) - response = { - statusCode: 500, - body: JSON.stringify({ "error": "Error writing data to table." }) - }; - } - - // Tracer: Close subsegment (the AWS Lambda one is closed automatically) - handlerSegment.close(); // (## index.handler) - - // Tracer: Set the facade segment as active again (the one created by AWS Lambda) - tracer.setSegment(segment); - - // All log statements are written to CloudWatch - logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); - - return response; + return response; }; From b6953dc65a33a69d77b975d7bd0f60305364a3e5 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 20 Apr 2022 14:08:54 +0000 Subject: [PATCH 14/25] added copied files to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 58ad859aea..e38e02e92e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ site # Generated API documentation (from TypeDoc) /api + +# SAM Example copies files +/examples/sam/src/handlers/*.ts \ No newline at end of file From d5969b87b2bef0744f80a6f5ea872e1e468d6e9b Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 20 Apr 2022 14:16:34 +0000 Subject: [PATCH 15/25] multiple changes to template.yaml --- examples/sam/template.yaml | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/examples/sam/template.yaml b/examples/sam/template.yaml index 90b8bb9a9e..e222fa0cbf 100644 --- a/examples/sam/template.yaml +++ b/examples/sam/template.yaml @@ -5,13 +5,23 @@ # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html AWSTemplateFormatVersion: 2010-09-09 Description: >- - sam-app + An example application with PowerTools for TypeScript instrumented. Its purpose is to demonstrate how to use the PowerTools with AWS SAM. The application an API with contains 3 endpoints (get all items, get an item by ids, put an item). Each Lambda function utilises Logger, Metrics, and Tracers. # Transform section specifies one or more macros that AWS CloudFormation uses to process your template # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html Transform: - AWS::Serverless-2016-10-31 +# Global configuration that all Functions inherit +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html +Globals: + Function: + Runtime: nodejs14.x + Architectures: + - x86_64 + MemorySize: 128 + Timeout: 100 + # Resources declares the AWS resources that you want to include in the stack # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html Resources: @@ -23,11 +33,6 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: src/handlers/get-all-items.getAllItemsHandler - Runtime: nodejs14.x - Architectures: - - x86_64 - MemorySize: 128 - Timeout: 100 Description: A simple example includes a HTTP get method to get all items from a DynamoDB table. Policies: # Give Create/Read/Update/Delete Permissions to the SampleTable @@ -64,11 +69,6 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: src/handlers/get-by-id.getByIdHandler - Runtime: nodejs14.x - Architectures: - - x86_64 - MemorySize: 128 - Timeout: 100 Description: A simple example includes a HTTP get method to get one item by id from a DynamoDB table. Policies: # Give Create/Read/Update/Delete Permissions to the SampleTable @@ -105,11 +105,6 @@ Resources: Type: AWS::Serverless::Function Properties: Handler: src/handlers/put-item.putItemHandler - Runtime: nodejs14.x - Architectures: - - x86_64 - MemorySize: 128 - Timeout: 100 Description: A simple example includes a HTTP post method to add one item to a DynamoDB table. Policies: # Give Create/Read/Update/Delete Permissions to the SampleTable @@ -148,9 +143,7 @@ Resources: PrimaryKey: Name: id Type: String - ProvisionedThroughput: - ReadCapacityUnits: 2 - WriteCapacityUnits: 2 + BillingMode: PAY_PER_REQUEST Outputs: WebEndpoint: From a35bb80d6df9f4d30bcf1b82ec3ee8211314ebfc Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 20 Apr 2022 14:19:03 +0000 Subject: [PATCH 16/25] changed policy to DynamoDBReadPolicy for all get* Lambdas --- examples/sam/template.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/sam/template.yaml b/examples/sam/template.yaml index e222fa0cbf..8e0ca54e2f 100644 --- a/examples/sam/template.yaml +++ b/examples/sam/template.yaml @@ -36,7 +36,7 @@ Resources: Description: A simple example includes a HTTP get method to get all items from a DynamoDB table. Policies: # Give Create/Read/Update/Delete Permissions to the SampleTable - - DynamoDBCrudPolicy: + - DynamoDBReadPolicy: TableName: !Ref SampleTable Tracing: Active Environment: @@ -72,7 +72,7 @@ Resources: Description: A simple example includes a HTTP get method to get one item by id from a DynamoDB table. Policies: # Give Create/Read/Update/Delete Permissions to the SampleTable - - DynamoDBCrudPolicy: + - DynamoDBReadPolicy: TableName: !Ref SampleTable Tracing: Active Environment: From 7a355cf74c13e9247529c80ff3f89335981343d8 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 20 Apr 2022 14:20:42 +0000 Subject: [PATCH 17/25] fixed escaped DynamodDB description in template.yaml --- examples/sam/template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sam/template.yaml b/examples/sam/template.yaml index 8e0ca54e2f..ca5215da2a 100644 --- a/examples/sam/template.yaml +++ b/examples/sam/template.yaml @@ -136,7 +136,7 @@ Resources: # Each Lambda function is defined by properties: # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction - # DynamoDB table to store item: {id: <ID>, name: <NAME>} + # DynamoDB table to store item: {id: , name: } SampleTable: Type: AWS::Serverless::SimpleTable Properties: From 5fd51c6f7faa421691c1712cc8874f31d742b1f0 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Tue, 26 Apr 2022 17:13:02 +0000 Subject: [PATCH 18/25] fixed README.md; changed try catch to capture missing env; import only DocumentClient --- .gitignore | 3 ++- examples/lambda-functions/get-all-items.ts | 31 +++++++++++++--------- examples/lambda-functions/get-by-id.ts | 19 ++++++------- examples/lambda-functions/put-item.ts | 19 ++++++------- examples/sam/README.md | 4 +-- examples/sam/template.yaml | 1 - 6 files changed, 43 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index e38e02e92e..ce7e2e2eb5 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ site /api # SAM Example copies files -/examples/sam/src/handlers/*.ts \ No newline at end of file +/examples/sam/src/handlers/* +!/examples/sam/src/handlers/COPY_LAMBDA_FUNCTIONS_HERE \ No newline at end of file diff --git a/examples/lambda-functions/get-all-items.ts b/examples/lambda-functions/get-all-items.ts index c1e7ff37c1..729c04ae62 100644 --- a/examples/lambda-functions/get-all-items.ts +++ b/examples/lambda-functions/get-all-items.ts @@ -2,7 +2,7 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda import { Metrics } from '@aws-lambda-powertools/metrics'; import { Logger } from '@aws-lambda-powertools/logger'; import { Tracer } from '@aws-lambda-powertools/tracer'; -import { DynamoDB } from 'aws-sdk'; +import { DocumentClient } from 'aws-sdk/clients/dynamodb'; // Create the PowerTools clients const metrics = new Metrics(); @@ -10,7 +10,7 @@ const logger = new Logger(); const tracer = new Tracer(); // Create DynamoDB DocumentClient and patch it for tracing -const docClient = tracer.captureAWS(new DynamoDB.DocumentClient()); +const docClient = tracer.captureAWSClient(new DocumentClient()); // Get the DynamoDB table name from environment variables const tableName = process.env.SAMPLE_TABLE; @@ -24,11 +24,11 @@ const tableName = process.env.SAMPLE_TABLE; * @returns {Object} object - API Gateway Lambda Proxy Output Format * */ - export const getAllItemsHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { if (event.httpMethod !== 'GET') { throw new Error(`getAllItems only accepts GET method, you tried: ${event.httpMethod}`); } + // Tracer: Get facade segment created by AWS Lambda const segment = tracer.getSegment(); @@ -46,21 +46,28 @@ export const getAllItemsHandler = async (event: APIGatewayProxyEvent, context: C // Metrics: Capture cold start metrics metrics.captureColdStartMetric(); - // All log statements are written to CloudWatch - logger.debug('received:', event); + // Logger: Add persistent attributes to each log statement + logger.addPersistentLogAttributes({ + awsRequestId: context.awsRequestId, + }); // get all items from the table (only first 1MB data, you can use `LastEvaluatedKey` to get the rest of data) // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html - const params = { - TableName: tableName! - }; - let response; - try { - const data = await docClient.scan(params).promise(); + if (!tableName) { + throw new Error('SAMPLE_TABLE environment variable is not set'); + } + + const data = await docClient.scan({ + TableName: tableName + }).promise(); const items = data.Items; + + // Logger: All log statements are written to CloudWatch + logger.debug(`retrieved items: ${items?.length || 0}`); + response = { statusCode: 200, body: JSON.stringify(items) @@ -84,4 +91,4 @@ export const getAllItemsHandler = async (event: APIGatewayProxyEvent, context: C logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); return response; -}; +}; \ No newline at end of file diff --git a/examples/lambda-functions/get-by-id.ts b/examples/lambda-functions/get-by-id.ts index 2461efb5fa..5ee0af0f45 100644 --- a/examples/lambda-functions/get-by-id.ts +++ b/examples/lambda-functions/get-by-id.ts @@ -2,7 +2,7 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda import { Metrics } from '@aws-lambda-powertools/metrics'; import { Logger } from '@aws-lambda-powertools/logger'; import { Tracer } from '@aws-lambda-powertools/tracer'; -import { DynamoDB } from 'aws-sdk'; +import { DocumentClient } from 'aws-sdk/clients/dynamodb'; // Create the PowerTools clients const metrics = new Metrics(); @@ -10,7 +10,7 @@ const logger = new Logger(); const tracer = new Tracer(); // Create DynamoDB DocumentClient and patch it for tracing -const docClient = tracer.captureAWS(new DynamoDB.DocumentClient()); +const docClient = tracer.captureAWS(new DocumentClient()); // Get the DynamoDB table name from environment variables const tableName = process.env.SAMPLE_TABLE; @@ -54,15 +54,16 @@ export const getByIdHandler = async (event: APIGatewayProxyEvent, context: Conte // Get the item from the table // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property - const params = { - TableName: tableName!, - Key: { id: id }, - }; - let response; - try { - const data = await docClient.get(params).promise(); + if (!tableName) { + throw new Error('SAMPLE_TABLE environment variable is not set'); + } + + const data = await docClient.get({ + TableName: tableName, + Key: { id: id }, + }).promise(); const item = data.Item; response = { statusCode: 200, diff --git a/examples/lambda-functions/put-item.ts b/examples/lambda-functions/put-item.ts index b946c5c10e..40addc6c5a 100644 --- a/examples/lambda-functions/put-item.ts +++ b/examples/lambda-functions/put-item.ts @@ -2,7 +2,7 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda import { Metrics } from '@aws-lambda-powertools/metrics'; import { Logger } from '@aws-lambda-powertools/logger'; import { Tracer } from '@aws-lambda-powertools/tracer'; -import { DynamoDB } from 'aws-sdk'; +import { DocumentClient } from 'aws-sdk/clients/dynamodb'; // Create the PowerTools clients const metrics = new Metrics(); @@ -10,7 +10,7 @@ const logger = new Logger(); const tracer = new Tracer(); // Create DynamoDB DocumentClient and patch it for tracing -const docClient = tracer.captureAWS(new DynamoDB.DocumentClient()); +const docClient = tracer.captureAWS(new DocumentClient()); // Get the DynamoDB table name from environment variables const tableName = process.env.SAMPLE_TABLE; @@ -56,15 +56,16 @@ export const putItemHandler = async (event: APIGatewayProxyEvent, context: Conte // Creates a new item, or replaces an old item with a new item // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property - const params = { - TableName: tableName!, - Item: { id: id, name: name } - }; - let response; - try { - await docClient.put(params).promise(); + if (!tableName) { + throw new Error('SAMPLE_TABLE environment variable is not set'); + } + + await docClient.put({ + TableName: tableName, + Item: { id: id, name: name } + }).promise(); response = { statusCode: 200, body: JSON.stringify(body) diff --git a/examples/sam/README.md b/examples/sam/README.md index 600b5ed9d6..37297e11ae 100644 --- a/examples/sam/README.md +++ b/examples/sam/README.md @@ -4,7 +4,7 @@ This project contains source code and supporting files for a serverless applicat This project includes the following files and folders: -- `src/handlers` - Code for the application's Lambda function written in TypeScript. +- `src/handlers` - Code for the application's Lambda function written in TypeScript. See "Prepare the project" below for instructions on how to copy the Lambda handler code here. - `events` - Invocation events that you can use to invoke the function. - `template.yaml` - A template that defines the application's AWS resources. @@ -115,7 +115,7 @@ You can find more information and examples about filtering Lambda function logs ## Cleanup -To delete the sample application that you created, run +To delete the sample application that you created, run the command below while in the `examples/sam` directory: ```bash sam delete diff --git a/examples/sam/template.yaml b/examples/sam/template.yaml index ca5215da2a..92bcf81fa9 100644 --- a/examples/sam/template.yaml +++ b/examples/sam/template.yaml @@ -143,7 +143,6 @@ Resources: PrimaryKey: Name: id Type: String - BillingMode: PAY_PER_REQUEST Outputs: WebEndpoint: From 17831a3dc735c34d54b7c2c3069304c09af76a2b Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Tue, 26 Apr 2022 17:18:16 +0000 Subject: [PATCH 19/25] fixed tracer.captureAWSClient to capture DocumentClient --- examples/lambda-functions/get-by-id.ts | 2 +- examples/lambda-functions/put-item.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/lambda-functions/get-by-id.ts b/examples/lambda-functions/get-by-id.ts index 5ee0af0f45..97f84298b4 100644 --- a/examples/lambda-functions/get-by-id.ts +++ b/examples/lambda-functions/get-by-id.ts @@ -10,7 +10,7 @@ const logger = new Logger(); const tracer = new Tracer(); // Create DynamoDB DocumentClient and patch it for tracing -const docClient = tracer.captureAWS(new DocumentClient()); +const docClient = tracer.captureAWSClient(new DocumentClient()); // Get the DynamoDB table name from environment variables const tableName = process.env.SAMPLE_TABLE; diff --git a/examples/lambda-functions/put-item.ts b/examples/lambda-functions/put-item.ts index 40addc6c5a..70f60a59ef 100644 --- a/examples/lambda-functions/put-item.ts +++ b/examples/lambda-functions/put-item.ts @@ -10,7 +10,7 @@ const logger = new Logger(); const tracer = new Tracer(); // Create DynamoDB DocumentClient and patch it for tracing -const docClient = tracer.captureAWS(new DocumentClient()); +const docClient = tracer.captureAWSClient(new DocumentClient()); // Get the DynamoDB table name from environment variables const tableName = process.env.SAMPLE_TABLE; From 3b68bdb230885c8ba95bf20180170f0ec13cca7c Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 27 Apr 2022 11:20:48 +0000 Subject: [PATCH 20/25] moved package.json to sam folder --- examples/lambda-functions/package.json | 25 ------------------------- examples/sam/package.json | 22 ++++++++++++++++++++++ examples/sam/template.yaml | 3 +-- 3 files changed, 23 insertions(+), 27 deletions(-) delete mode 100644 examples/lambda-functions/package.json create mode 100644 examples/sam/package.json diff --git a/examples/lambda-functions/package.json b/examples/lambda-functions/package.json deleted file mode 100644 index ce999ad818..0000000000 --- a/examples/lambda-functions/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "hello_world", - "version": "1.0.0", - "description": "AWS Lambda Powertools for TypeScript Sample", - "author": "SAM CLI", - "license": "MIT", - "dependencies": { - "@aws-lambda-powertools/logger": "^0.7.0", - "@aws-lambda-powertools/metrics": "^0.7.0", - "@aws-lambda-powertools/tracer": "^0.7.0" - }, - "scripts": { - "lint": "eslint '*.ts' --quiet --fix", - "compile": "tsc" - }, - "devDependencies": { - "@types/aws-lambda": "^8.10.86", - "@types/node": "17.0.10", - "@typescript-eslint/parser": "^5.12.1", - "esbuild": "^0.14.23", - "eslint": "^8.4.0", - "ts-node": "^10.0.0", - "typescript": "^4.1.3" - } -} diff --git a/examples/sam/package.json b/examples/sam/package.json new file mode 100644 index 0000000000..bc55b7681a --- /dev/null +++ b/examples/sam/package.json @@ -0,0 +1,22 @@ +{ + "name": "powertools-typescript-sam-example", + "version": "1.0.0", + "description": "This project contains source code and supporting files for a serverless application that you can deploy with the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API.", + "license": "MIT", + "devDependencies": { + "@types/aws-lambda": "^8.10.86", + "@types/node": "17.0.10", + "@typescript-eslint/parser": "^5.12.1", + "esbuild": "^0.14.23", + "eslint": "^8.4.0", + "ts-node": "^10.0.0", + "typescript": "^4.1.3" + }, + "dependencies": { + "@aws-lambda-powertools/logger": "^0.7.0", + "@aws-lambda-powertools/metrics": "^0.7.0", + "@aws-lambda-powertools/tracer": "^0.7.0", + "aws-sdk": "^2.1122.0" + } + } + \ No newline at end of file diff --git a/examples/sam/template.yaml b/examples/sam/template.yaml index 92bcf81fa9..088b74e4cd 100644 --- a/examples/sam/template.yaml +++ b/examples/sam/template.yaml @@ -9,8 +9,7 @@ Description: >- # Transform section specifies one or more macros that AWS CloudFormation uses to process your template # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html -Transform: -- AWS::Serverless-2016-10-31 +Transform: AWS::Serverless-2016-10-31 # Global configuration that all Functions inherit # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html From de14ec6b40a0a4beb2c3532ebbb0a3e162d20dbe Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 27 Apr 2022 11:26:20 +0000 Subject: [PATCH 21/25] changed author in package.json; various changes to README.md; changed logger example in get-by-id and put-item to match get-all-items; --- examples/lambda-functions/get-by-id.ts | 6 ++++-- examples/lambda-functions/put-item.ts | 6 ++++-- examples/sam/README.md | 6 +++--- examples/sam/package.json | 4 ++++ 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/examples/lambda-functions/get-by-id.ts b/examples/lambda-functions/get-by-id.ts index 97f84298b4..5dcc2b1d2b 100644 --- a/examples/lambda-functions/get-by-id.ts +++ b/examples/lambda-functions/get-by-id.ts @@ -46,8 +46,10 @@ export const getByIdHandler = async (event: APIGatewayProxyEvent, context: Conte // Metrics: Capture cold start metrics metrics.captureColdStartMetric(); - // All log statements are written to CloudWatch - logger.debug('received:', event); + // Logger: Add persistent attributes to each log statement + logger.addPersistentLogAttributes({ + awsRequestId: context.awsRequestId, + }); // Get id from pathParameters from APIGateway because of `/{id}` at template.yaml const id = event.pathParameters!.id; diff --git a/examples/lambda-functions/put-item.ts b/examples/lambda-functions/put-item.ts index 70f60a59ef..8be50f9ca6 100644 --- a/examples/lambda-functions/put-item.ts +++ b/examples/lambda-functions/put-item.ts @@ -46,8 +46,10 @@ export const putItemHandler = async (event: APIGatewayProxyEvent, context: Conte // Metrics: Capture cold start metrics metrics.captureColdStartMetric(); - // All log statements are written to CloudWatch - logger.debug('received:', event); + // Logger: Add persistent attributes to each log statement + logger.addPersistentLogAttributes({ + awsRequestId: context.awsRequestId, + }); // Get id and name from the body of the request const body = JSON.parse(event.body!); diff --git a/examples/sam/README.md b/examples/sam/README.md index 37297e11ae..367473d2d4 100644 --- a/examples/sam/README.md +++ b/examples/sam/README.md @@ -29,15 +29,15 @@ You will need to have a valid AWS Account in order to deploy these resources. Th ## Prepare the project -All following commands must be executed inside the folder `examples/sam` +All the following commands must be executed inside the folder `examples/sam` -Before deploying this example, copy the Lambda Function Code to `src/handlers`, +Before deploying this example, copy the Lambda Function Code to `src/handlers` by running: ```bash cp -R ../lambda-functions/* ./src/handlers ``` -and install the npm dependencies +and install the npm dependencies: ```bash npm i diff --git a/examples/sam/package.json b/examples/sam/package.json index bc55b7681a..6ac7860887 100644 --- a/examples/sam/package.json +++ b/examples/sam/package.json @@ -1,6 +1,10 @@ { "name": "powertools-typescript-sam-example", "version": "1.0.0", + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com" + }, "description": "This project contains source code and supporting files for a serverless application that you can deploy with the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API.", "license": "MIT", "devDependencies": { From 48425c0f88ee827b78bef6a608f55d1aec5d33bf Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 27 Apr 2022 12:53:01 +0000 Subject: [PATCH 22/25] removed redundant comments --- examples/sam/template.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/sam/template.yaml b/examples/sam/template.yaml index 088b74e4cd..c71a8c6ae4 100644 --- a/examples/sam/template.yaml +++ b/examples/sam/template.yaml @@ -60,8 +60,6 @@ Resources: Sourcemap: true, EntryPoints: - src/handlers/get-all-items.ts - # Each Lambda function is defined by properties: - # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction # This is a Lambda function config associated with the source code: get-by-id.js getByIdFunction: @@ -96,8 +94,6 @@ Resources: Sourcemap: true, EntryPoints: - src/handlers/get-by-id.ts - # Each Lambda function is defined by properties: - # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction # This is a Lambda function config associated with the source code: put-item.js putItemFunction: @@ -132,8 +128,6 @@ Resources: Sourcemap: true, EntryPoints: - src/handlers/put-item.ts - # Each Lambda function is defined by properties: - # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction # DynamoDB table to store item: {id: , name: } SampleTable: From 6b55f2dffc958f262a3da90b351d84b81cd5a354 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 27 Apr 2022 15:36:44 +0000 Subject: [PATCH 23/25] added additional steps to readme.md --- examples/sam/README.md | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/examples/sam/README.md b/examples/sam/README.md index 367473d2d4..003a21382b 100644 --- a/examples/sam/README.md +++ b/examples/sam/README.md @@ -64,6 +64,50 @@ The first command will build the source of your application. Using esbuild for b You can find your API Gateway Endpoint URL in the output values displayed after deployment. +## Execute the functions via API Gateway + +Use the API Gateway Endpoint URL from the output values to execute the functions. First, let's add two items to the DynamoDB Table by running: + +```bash +curl -XPOST --header 'Content-Type: application/json' --data '{"id":"myfirstitem","name":"Some Name for the first item"}' https://randomid12345.execute-api.eu-central-1.amazonaws.com/Prod/ +curl -XPOST --header 'Content-Type: application/json' --data '{"id":"myseconditem","name":"Some Name for the second item"}' https://randomid1245.execute-api.eu-central-1.amazonaws.com/Prod/ +```` + +Now, let's retrieve all items by running: + +```bash +curl -XGET https://randomid12345.execute-api.eu-central-1.amazonaws.com/Prod/ +```` + +And finally, let's retrieve a specific item by running: +```bash +https://randomid12345.execute-api.eu-central-1.amazonaws.com/Prod/myseconditem/ +``` + +## Observe the outputs in AWS CloudWatch & X-Ray +### CloudWatch + +If we check the logs in CloudWatch, we can see that the logs are structured like this +``` +2022-04-26T17:00:23.808Z e8a51294-6c6a-414c-9777-6b0f24d8739b DEBUG +{ + "level": "DEBUG", + "message": "retrieved items: 0", + "service": "getAllItems", + "timestamp": "2022-04-26T17:00:23.808Z", + "awsRequestId": "e8a51294-6c6a-414c-9777-6b0f24d8739b" +} +``` + +By having structured logs like this, we can easily search and analyse them in [CloudWatch Logs Insight](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html). Run the following query to get all messages for a specific `awsRequestId`: + +```` +filter awsRequestId="bcd50969-3a55-49b6-a997-91798b3f133a" + | fields timestamp, message +```` +### AWS X-Ray +As we have enabled tracing for our Lambda-Funtions, we can visit [AWS X-Ray Console](https://console.aws.amazon.com/xray/home#/traces/) and see [traces](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-traces) and a [service map](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-using-xray-maps.html) for our application. + ## Use the SAM CLI to build and test locally Build your application with the `sam build` command. From 310c39f608c329879892b675dba6faf3b00b95b4 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Wed, 27 Apr 2022 15:42:56 +0000 Subject: [PATCH 24/25] removed null assertions --- examples/lambda-functions/get-by-id.ts | 11 +++++++---- examples/lambda-functions/put-item.ts | 13 ++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/examples/lambda-functions/get-by-id.ts b/examples/lambda-functions/get-by-id.ts index 5dcc2b1d2b..8e498fd701 100644 --- a/examples/lambda-functions/get-by-id.ts +++ b/examples/lambda-functions/get-by-id.ts @@ -51,9 +51,6 @@ export const getByIdHandler = async (event: APIGatewayProxyEvent, context: Conte awsRequestId: context.awsRequestId, }); - // Get id from pathParameters from APIGateway because of `/{id}` at template.yaml - const id = event.pathParameters!.id; - // Get the item from the table // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property let response; @@ -61,10 +58,16 @@ export const getByIdHandler = async (event: APIGatewayProxyEvent, context: Conte if (!tableName) { throw new Error('SAMPLE_TABLE environment variable is not set'); } + if (!event.pathParameters) { + throw new Error('event does not contain pathParameters') + } + if (!event.pathParameters.id) { + throw new Error('PathParameter id is missing') + } const data = await docClient.get({ TableName: tableName, - Key: { id: id }, + Key: { id: event.pathParameters.id }, }).promise(); const item = data.Item; response = { diff --git a/examples/lambda-functions/put-item.ts b/examples/lambda-functions/put-item.ts index 8be50f9ca6..392689815d 100644 --- a/examples/lambda-functions/put-item.ts +++ b/examples/lambda-functions/put-item.ts @@ -51,11 +51,6 @@ export const putItemHandler = async (event: APIGatewayProxyEvent, context: Conte awsRequestId: context.awsRequestId, }); - // Get id and name from the body of the request - const body = JSON.parse(event.body!); - const id = body.id; - const name = body.name; - // Creates a new item, or replaces an old item with a new item // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property let response; @@ -63,6 +58,14 @@ export const putItemHandler = async (event: APIGatewayProxyEvent, context: Conte if (!tableName) { throw new Error('SAMPLE_TABLE environment variable is not set'); } + if (!event.body) { + throw new Error('Event does not contain body') + } + + // Get id and name from the body of the request + const body = JSON.parse(event.body); + const id = body.id; + const name = body.name; await docClient.put({ TableName: tableName, From ae2dddf00fa70c43c1966f4e68f1771851267733 Mon Sep 17 00:00:00 2001 From: Benedikt Pauwels Date: Fri, 6 May 2022 13:02:11 +0000 Subject: [PATCH 25/25] removed manual copy step and created a symlink instead --- examples/sam/README.md | 10 ++-------- examples/sam/src/handlers | 1 + examples/sam/src/handlers/COPY_LAMBDA_FUNCTIONS_HERE | 0 3 files changed, 3 insertions(+), 8 deletions(-) create mode 120000 examples/sam/src/handlers delete mode 100644 examples/sam/src/handlers/COPY_LAMBDA_FUNCTIONS_HERE diff --git a/examples/sam/README.md b/examples/sam/README.md index 003a21382b..a96692f561 100644 --- a/examples/sam/README.md +++ b/examples/sam/README.md @@ -29,15 +29,9 @@ You will need to have a valid AWS Account in order to deploy these resources. Th ## Prepare the project -All the following commands must be executed inside the folder `examples/sam` +All the following commands in this file must be executed inside the folder `examples/sam` -Before deploying this example, copy the Lambda Function Code to `src/handlers` by running: - -```bash -cp -R ../lambda-functions/* ./src/handlers -``` - -and install the npm dependencies: +Before deploying this example install the npm dependencies: ```bash npm i diff --git a/examples/sam/src/handlers b/examples/sam/src/handlers new file mode 120000 index 0000000000..4407535789 --- /dev/null +++ b/examples/sam/src/handlers @@ -0,0 +1 @@ +../../lambda-functions/ \ No newline at end of file diff --git a/examples/sam/src/handlers/COPY_LAMBDA_FUNCTIONS_HERE b/examples/sam/src/handlers/COPY_LAMBDA_FUNCTIONS_HERE deleted file mode 100644 index e69de29bb2..0000000000