├── .gitignore ├── README.md ├── package.json ├── src ├── index.js ├── package-lock.json ├── package.json └── process-response.js └── template.yml /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm_debug.log 3 | .vscode 4 | .DS_Store 5 | .idea 6 | output.yml 7 | src/node_modules 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Api Gateway -> Lambda (Put) -> DynamoDB 3 | 4 | ## Description 5 | 6 | This is a serverless component consisting of: 7 | 8 | - an API Gateway, receiving request data 9 | - a Lambda function, that processes that data and saves 10 | - a DynamoDB table, where all your data is stored. 11 | 12 | Aside from this main functionality, its important features are: 13 | 14 | - Supports CORS 15 | - Written in Node.js 16 | - Easily composable into your other app components by adding triggers to its DynamoDB table 17 | 18 | ## Latest Release - 2.1.0 19 | 20 | Upgrading the Node.js release to 12.x LTS 21 | 22 | ## Future Release 23 | 24 | Switch to AWS CDK 25 | 26 | ## Roadmap - Upcoming changes 27 | 28 | Here are the upcoming changes that I'll add to this serverless component: 29 | 30 | - TypeScript 31 | - Potentially converting to an AWS CDK component on SAR 32 | - ESLint 33 | - Tests 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-lambda-save-dynamodb", 3 | "version": "2.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "package": "aws cloudformation package --template-file template.yml --output-template-file output.yml --s3-bucket app-repo-components", 8 | "deploy": "aws cloudformation deploy --template-file output.yml --stack-name api-lambda-save-dynamodb --capabilities CAPABILITY_IAM --parameter-overrides TableName=$npm_package_config_table_name", 9 | "qd": "npm run package && npm run deploy" 10 | }, 11 | "config": { 12 | "table_name": "desks" 13 | }, 14 | "keywords": [], 15 | "author": "Aleksandar Simovic ", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | const dynamoDb = new AWS.DynamoDB.DocumentClient(); 3 | const uuidv4 = require('uuid/v4'); 4 | const processResponse = require('./process-response.js'); 5 | const TABLE_NAME = process.env.TABLE_NAME, 6 | PRIMARY_KEY = process.env.PRIMARY_KEY, 7 | IS_CORS = true; 8 | 9 | exports.handler = async event => { 10 | if (event.httpMethod === 'OPTIONS') { 11 | return processResponse(IS_CORS); 12 | } 13 | if (!event.body) { 14 | return processResponse(IS_CORS, 'invalid', 400); 15 | } 16 | const item = JSON.parse(event.body); 17 | item[PRIMARY_KEY] = uuidv4(); 18 | const params = { 19 | TableName: TABLE_NAME, 20 | Item: item 21 | } 22 | try { 23 | await dynamoDb.put(params).promise() 24 | return processResponse(IS_CORS); 25 | } catch (error) { 26 | let errorResponse = `Error: Execution update, caused a Dynamodb error, please look at your logs.`; 27 | if (error.code === 'ValidationException') { 28 | if (error.message.includes('reserved keyword')) errorResponse = `Error: You're using AWS reserved keywords as attributes`; 29 | } 30 | console.log(error); 31 | return processResponse(IS_CORS, errorResponse, 500); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-lambda-save-dynamodb", 3 | "version": "2.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "uuid": { 8 | "version": "3.4.0", 9 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 10 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-lambda-save-dynamodb", 3 | "version": "2.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "devDependencies": {}, 7 | "keywords": [], 8 | "author": "Aleksandar Simovic ", 9 | "license": "MIT", 10 | "dependencies": { 11 | "uuid": "^3.4.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/process-response.js: -------------------------------------------------------------------------------- 1 | module.exports = (isCors, body, statusCode) => { 2 | const status = statusCode || (body ? 200 : 204); 3 | const headers = { 'Content-Type': 'application/json' }; 4 | if (isCors) { 5 | Object.assign(headers, { 6 | 'Access-Control-Allow-Headers': 'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token', 7 | 'Access-Control-Allow-Methods': 'OPTIONS,POST', 8 | 'Access-Control-Allow-Origin': '*', 9 | 'Access-Control-Max-Age': '86400' 10 | }); 11 | } 12 | return { 13 | statusCode: status, 14 | body: JSON.stringify(body) || '', 15 | headers: headers 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Transform: AWS::Serverless-2016-10-31 3 | Parameters: 4 | TableName: 5 | Type: String 6 | Description: (Required) The name of the new DynamoDB table you want to create and save to. Minimum 3 characters 7 | MinLength: 3 8 | MaxLength: 50 9 | AllowedPattern: ^[A-Za-z_]+$ 10 | ConstraintDescription: 'Required. Can be characters and underscore only. No numbers or special characters allowed.' 11 | CorsOrigin: 12 | Type: String 13 | Default: "'*'" 14 | Description: (Optional) Cross-origin resource sharing (CORS) Origin. You can specify a single origin, all "*" or leave empty and no CORS will be applied. 15 | MaxLength: 250 16 | Resources: 17 | SaveApi: 18 | Type: AWS::Serverless::Api 19 | Properties: 20 | Cors: 21 | AllowHeaders: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'" 22 | AllowOrigin: !Ref CorsOrigin 23 | MaxAge: "'3600'" 24 | AllowMethods: "'OPTIONS,POST'" 25 | EndpointConfiguration: REGIONAL 26 | TracingEnabled: true 27 | StageName: prod 28 | LambdaSaver: 29 | Type: AWS::Serverless::Function 30 | Properties: 31 | Handler: index.handler 32 | Runtime: nodejs12.x 33 | CodeUri: src/ 34 | Environment: 35 | Variables: 36 | TABLE_NAME: !Ref TableName 37 | CORS_ORIGIN: !Ref CorsOrigin 38 | PRIMARY_KEY: !Sub ${TableName}Id 39 | Policies: 40 | - DynamoDBCrudPolicy: 41 | TableName: !Ref TableName 42 | Events: 43 | SaveApi: 44 | Type: Api 45 | Properties: 46 | Path: /save 47 | Method: POST 48 | RestApiId: !Ref SaveApi 49 | DynamoDBTable: 50 | Type: AWS::DynamoDB::Table 51 | Properties: 52 | TableName: !Ref TableName 53 | AttributeDefinitions: 54 | - AttributeName: !Sub ${TableName}Id 55 | AttributeType: S 56 | KeySchema: 57 | - AttributeName: !Sub ${TableName}Id 58 | KeyType: HASH 59 | BillingMode: PAY_PER_REQUEST 60 | SSESpecification: 61 | SSEEnabled: True 62 | StreamSpecification: 63 | StreamViewType: NEW_IMAGE 64 | Outputs: 65 | ApiUrl: 66 | Value: !Sub https://${SaveApi}.execute-api.${AWS::Region}.amazonaws.com/prod/save 67 | Description: The URL of the API Gateway you invoke to save to your DynamoDB Table. 68 | DynamoDBTable: 69 | Value: !Ref TableName 70 | Description: The name of your DynamoDB Table 71 | DynamoDBTableStreamArn: 72 | Value: !GetAtt DynamoDBTable.StreamArn 73 | Description: The ARN of your DynamoDB Table Stream 74 | --------------------------------------------------------------------------------