├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .prettierignore ├── DEVGUIDE.md ├── Makefile ├── README.md ├── __tests__ └── unit │ └── handlers │ └── .gitkeep ├── bin └── install.js ├── events └── sample-event.json ├── jest.config.ts ├── package.json ├── prettier.config.js ├── samconfig.toml ├── serverless.yml ├── src ├── handlers │ └── public-api@v1.ts └── lib │ ├── HttpResponse.ts │ └── helpers.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aws-sam -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | parserOptions: { 4 | ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features 5 | sourceType: "module" 6 | }, 7 | extends: [ 8 | "plugin:@typescript-eslint/recommended", // recommended rules from the @typescript-eslint/eslint-plugin 9 | "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. 10 | ], 11 | rules: { 12 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 13 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 14 | } 15 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | 99 | # parcel-bundler cache (https://parceljs.org/) 100 | .cache 101 | .parcel-cache 102 | 103 | # Next.js build output 104 | .next 105 | 106 | # Nuxt.js build / generate output 107 | .nuxt 108 | dist 109 | 110 | # Storybook build outputs 111 | .out 112 | .storybook-out 113 | storybook-static 114 | 115 | # rollup.js default build output 116 | dist/ 117 | 118 | # Gatsby files 119 | .cache/ 120 | # Comment in the public line in if your project uses Gatsby and not Next.js 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | # public 123 | 124 | # vuepress build output 125 | .vuepress/dist 126 | 127 | # Serverless directories 128 | .serverless/ 129 | 130 | # FuseBox cache 131 | .fusebox/ 132 | 133 | # DynamoDB Local files 134 | .dynamodb/ 135 | 136 | # TernJS port file 137 | .tern-port 138 | 139 | # Stores VSCode versions used for testing VSCode extensions 140 | .vscode-test 141 | 142 | # Temporary folders 143 | tmp/ 144 | temp/ 145 | 146 | ### OSX ### 147 | # General 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | 156 | # Thumbnails 157 | ._* 158 | 159 | # Files that might appear in the root of a volume 160 | .DocumentRevisions-V100 161 | .fseventsd 162 | .Spotlight-V100 163 | .TemporaryItems 164 | .Trashes 165 | .VolumeIcon.icns 166 | .com.apple.timemachine.donotpresent 167 | 168 | # Directories potentially created on remote AFP share 169 | .AppleDB 170 | .AppleDesktop 171 | Network Trash Folder 172 | Temporary Items 173 | .apdisk 174 | 175 | ### SAM ### 176 | # Ignore build directories for the AWS Serverless Application Model (SAM) 177 | # Info: https://aws.amazon.com/serverless/sam/ 178 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 179 | 180 | **/.aws-sam 181 | 182 | ### Windows ### 183 | # Windows thumbnail cache files 184 | Thumbs.db 185 | Thumbs.db:encryptable 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | *.lnk 205 | 206 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 207 | ## Application Level ## 208 | 209 | # aws sam 210 | **/.aws-sam 211 | **/.dist 212 | # modules 213 | **/node_modules 214 | # sam config 215 | # samconfig.toml 216 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/.aws-sam 3 | **/.cache 4 | **/coverage 5 | package-lock.json 6 | .DS_Store -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.yaml 2 | *.yml 3 | *.json 4 | *.md 5 | jest.config.ts -------------------------------------------------------------------------------- /DEVGUIDE.md: -------------------------------------------------------------------------------- 1 | ## Developer Guide 2 | 3 | install `awscliv2`, `aws-sam-cli`, `nodejs 18` (and `make` only for macOS and Linux) on your machine and ensure that your aws iam account is configured. the configuration profile should be located at `~/.aws/config`, access credentials should be located at `~/.aws/credentials`. 4 | 5 | To build and deploy your application for the first time, run the following commands in your shell using `makefile` (only for macOS and Linux): 6 | 7 | ```bash 8 | make install 9 | make build 10 | make deploy 11 | 12 | # to delete the deployment 13 | make destroy 14 | ``` 15 | 16 | The second command will build the source of your application. The 3rd command will package and deploy your application to AWS, with a series of prompts: 17 | 18 | * **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. 19 | * **AWS Region**: The AWS region you want to deploy your app to. 20 | * **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. 21 | * **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. 22 | * **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. 23 | 24 | You can find your API Gateway Endpoint URL in the output values displayed after deployment. 25 | 26 | ## Use the SAM CLI to build and test locally 27 | 28 | Build your application with the `make build` command. 29 | 30 | ```bash 31 | ts-lambda-kit$ make build 32 | ``` 33 | 34 | The SAM CLI installs dependencies defined in `package.json`, compiles TypeScript with esbuild, creates a deployment package, and saves it in the `.aws-sam/build` folder. 35 | 36 | 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. 37 | 38 | Run functions locally and invoke them with the `sam local invoke` command. 39 | 40 | ```bash 41 | ts-lambda-kit$ sam local invoke {lambda-function-name} --event events/event.json 42 | ``` 43 | 44 | The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. 45 | 46 | ```bash 47 | ts-lambda-kit$ sam local start-api 48 | ts-lambda-kit$ curl http://localhost:3000/ 49 | ``` 50 | 51 | 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. 52 | 53 | ```yaml 54 | Events: 55 | ApiGatewayRouteGetUsers: 56 | Type: Api 57 | Properties: 58 | Path: /users 59 | Method: GET 60 | RestApiId: 61 | Ref: RestApiGateway 62 | ``` 63 | 64 | ## Add a resource to your application 65 | 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. 66 | 67 | ## Fetch, tail, and filter Lambda function logs 68 | 69 | 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. 70 | 71 | `NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. 72 | 73 | ```bash 74 | ts-lambda-kit$ sam logs -n {lambda-function-name} --stack-name typescript-aws-lambda-serverless-restapi-kit --tail 75 | ``` 76 | 77 | 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). 78 | 79 | ## Unit tests 80 | 81 | Tests are defined in the `__tests__` folder in this project. Use NPM to install the [Jest test framework](https://jestjs.io/) and run unit tests. 82 | 83 | ```bash 84 | ts-lambda-kit$ npm install 85 | ts-lambda-kit$ npm run test 86 | ``` 87 | 88 | ## Cleanup 89 | 90 | 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: 91 | 92 | ```bash 93 | make destroy 94 | ``` 95 | 96 | ## Resources 97 | 98 | 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. 99 | 100 | 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/) 101 | 102 | 103 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build build_cloudformation build_typescript clean clean_cache clean_dist clean_modules clean_tmp deploy deployci deploy_cloudformation destroy destroy_cloudformation lint lintfix 2 | 3 | #### input params for aws cloud 4 | #### Please change the value as required like region, stack & profile 5 | 6 | debug = debug 7 | maintenance = false 8 | region = eu-central-1 9 | stack = typescript-lambda-backend-app # decide a for the app 10 | stage = v1 11 | substage = sazal # add x/ 12 | tests = unit 13 | #### 14 | 15 | #### cloud params 16 | profile = sazal-dev # add the name of aws cli configured profile 17 | stackname = ${stack} 18 | #### 19 | 20 | 21 | 22 | #### info 23 | help: 24 | @echo "" 25 | @echo "supported targets:" 26 | @echo "" 27 | @echo "- build: builds service. runs subtargets." 28 | @echo " - build_cloudformation: builds cloudformation." 29 | @echo " - build_typescript: builds typescript." 30 | @echo "" 31 | @echo "- clean: cleans service. runs subtargets." 32 | @echo " - clean_cache: cleans .cache folder." 33 | @echo " - clean_dist: cleans .aws-sam folder." 34 | @echo " - clean_modules: cleans node_modules folder." 35 | @echo " - clean_tmp: cleans .tmp folder." 36 | @echo "" 37 | @echo "- deploy: deploys service. runs build. runs subtargets." 38 | @echo " - deploy_cloudformation: deploys cloudformation." 39 | @echo "" 40 | @echo "- destroy: destroys service. runs subtargets." 41 | @echo " - destroy_cloudformation: destroys cloudformation." 42 | @echo "" 43 | @echo "- lint: lints service." 44 | @echo "- lintfix: lint fix service." 45 | @echo "" 46 | @echo "- test: for testing all the features by jest." 47 | @echo "" 48 | #### 49 | 50 | #### validate 51 | install: 52 | @npm i 53 | 54 | #### validate 55 | validate: 56 | @sam validate -t serverless.yml --profile sazal-dev 57 | 58 | #### build 59 | build: build_typescript build_cloudformation 60 | 61 | build_cloudformation: 62 | @sam build -b .aws-sam/build -t serverless.yml \ 63 | --cached \ 64 | --cache-dir .cache \ 65 | --parallel 66 | 67 | build_typescript: 68 | @npx --no-install tsc 69 | #### 70 | 71 | 72 | #### clean 73 | clean: clean_cache clean_dist clean_modules clean_coverage clean_tmp 74 | 75 | clean_cache: 76 | @rm -rf .cache 77 | 78 | clean_dist: 79 | @rm -rf .aws-sam 80 | 81 | clean_modules: 82 | # @rm -rf node_modules 83 | 84 | clean_coverage: 85 | @rm -rf coverage 86 | 87 | clean_tmp: 88 | @rm -rf .tmp 89 | #### 90 | 91 | 92 | #### deploy 93 | deploy: build deploy_cloudformation 94 | 95 | deployci: deploy_cloudformation 96 | 97 | deploy_cloudformation: 98 | @sam deploy -t .aws-sam/build/template.yaml \ 99 | --capabilities CAPABILITY_IAM \ 100 | --no-fail-on-empty-changeset \ 101 | --parameter-overrides \ 102 | DEBUG=${debug} \ 103 | MAINTENANCE=${maintenance} \ 104 | STACK=${stack} \ 105 | STAGE=${stage} \ 106 | SUBSTAGE=${substage} \ 107 | --profile ${profile} \ 108 | --region ${region} \ 109 | --resolve-s3 \ 110 | --stack-name ${stackname} 111 | #### 112 | 113 | 114 | #### destroy 115 | destroy: destroy_cloudformation 116 | 117 | destroy_cloudformation: 118 | @sam delete \ 119 | --no-prompts \ 120 | --profile ${profile} \ 121 | --region ${region} \ 122 | --stack-name ${stackname} 123 | #### 124 | 125 | 126 | #### lint 127 | lint: 128 | @npx --no-install prettier --check '{src,__tests__}/**/*.{js,ts}' 129 | 130 | lintfix: 131 | @npx --no-install prettier --write '{src,__tests__}/**/*.{js,ts}' 132 | #### 133 | 134 | #### Test 135 | test: 136 | @npm run test 137 | 138 | #### 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `ts-lambda-kit` 2 | 3 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com) 4 | [![npm version](https://badge.fury.io/js/ts-lambda-kit.svg)](https://badge.fury.io/js/ts-lambda-kit) 5 | 6 | #### A quick-start kit to Build REST API's using Typescript, AWS Lambda & SAM CLI. 7 | 8 | 9 | By running single command, you will get a `production-ready` [TypeScript](https://typescriptlang.org) lambda application and fully configured on your machine. Our goal is to provide you with a very cool, opinionated architecture. It will provide you with things like generic functionalities for the [lambda](https://docs.aws.amazon.com/lambda/) function and cool request handling support with [Amazon API Gateway](https://aws.amazon.com/api-gateway/), [AWS CloudFormation](https://aws.amazon.com/cloudformation/), and [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). 10 | 11 | It makes things very easy when the question comes with build, and deploy. It has a strong focus on making sure everything is developer-friendly. 12 | 13 | 14 | ### Requirements: 15 | 16 | * Node.js - [Install Node.js 18](https://nodejs.org/en/) 17 | * NPM 18 | * TypeScript 19 | * Git 20 | * SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) 21 | * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) optional. 22 | 23 | 24 | ## Quick Installation 25 | 26 | To create a project, simply run: 27 | 28 | ```bash 29 | npx ts-lambda-kit 30 | ``` 31 | 32 | 33 | #### 🥇 Cool! You are ready to make your amazing product. 34 | #### 🎯 I know you liked it. Please, give a star to the [repository](https://github.com/DevSazal/ts-lambda-kit.git) ⭐⭐⭐ 35 |
36 | 37 | ## Architecture 38 | 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. 39 | 40 | - `src` - Code for the application's Lambda function written in TypeScript. 41 | - `events` - Invocation events that you can use to invoke the function. 42 | - `__tests__` - Unit tests for the application code. 43 | - `serverless.yml` - A template that defines the application's AWS resources. 44 | 45 | The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `serverless.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. 46 | 47 | ## Developer Guide 48 | 49 | install `awscliv2`, `aws-sam-cli`, `nodejs 18` (and `make` only for macOS and Linux) on your machine and ensure that your aws iam account is configured. the configuration profile should be located at `~/.aws/config`, access credentials should be located at `~/.aws/credentials`. 50 | 51 | To build and deploy your application for the first time, run the following commands in your shell using `makefile` (only for macOS and Linux): 52 | 53 | ```bash 54 | make install 55 | make build 56 | ``` 57 | 58 | Before deployment, open the `Makefile` and add your AWS-configured profile name at the `profile` variable. 59 | 60 | Wait, you may try this command on your terminal to check what is the name of your aws-configured profile: 61 | 62 | ```bash 63 | aws configure list-profiles 64 | ``` 65 | 66 | #### Let's make it live: 67 | 68 | ```bash 69 | make deploy 70 | ``` 71 | 72 | The 2nd command will build the source of your application. The 3rd command will package and deploy your application to AWS, with a series of prompts: 73 | 74 | * **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. 75 | * **AWS Region**: The AWS region you want to deploy your app to. 76 | * **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. 77 | * **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. 78 | * **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. 79 | 80 | You can find your API Gateway Endpoint URL in the output values displayed after deployment. 81 | 82 | #### 🎯 For more information, please check out our [details guide](https://github.com/DevSazal/ts-lambda-kit/blob/main/DEVGUIDE.md) 83 |
84 | 85 | ## Key Resources 86 | 87 | 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. 88 | 89 | ## Contributing 90 | 91 | Contributions are more than welcome! We are excited to explore how we can create something truly remarkable together! 92 | 93 | ## License 94 | 95 | ISC © 2023 96 | -------------------------------------------------------------------------------- /__tests__/unit/handlers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSazal/ts-lambda-kit/74f059b6e14e1376cfcd33cf505c141da469e265/__tests__/unit/handlers/.gitkeep -------------------------------------------------------------------------------- /bin/install.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { execSync } = require('child_process'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | 7 | // Validate Arguments 8 | if (process.argv.length < 3) { 9 | console.log('You have to provide a name for your lambda backend app.'); 10 | console.log('For example :'); 11 | console.log(' npx ts-lambda-kit my-app'); 12 | // console.log(' OR'); 13 | // console.log(' npm init ts-lambda-kit my-app'); 14 | process.exit(1); 15 | } 16 | 17 | // define constants 18 | const projectName = process.argv[2]; 19 | const currentPath = process.cwd(); 20 | const projectPath = path.join(currentPath, projectName); 21 | const git_repo = 'https://github.com/DevSazal/ts-lambda-kit.git'; 22 | 23 | // check if directory already exists 24 | try { 25 | fs.mkdirSync(projectPath); 26 | } catch (err) { 27 | if (err.code === 'EEXIST') { 28 | console.log(`The folder ${projectName} already exist in the current directory, please give it another name.`); 29 | } else { 30 | console.log(error); 31 | } 32 | process.exit(1); 33 | } 34 | 35 | async function main() { 36 | try { 37 | console.log('\n'); 38 | console.log(`Downloading files...`); 39 | console.log(`repository: ${git_repo}`); 40 | console.log('\n'); 41 | execSync(`git clone --depth 1 ${git_repo} ${projectPath}`); 42 | 43 | process.chdir(projectPath); 44 | 45 | console.log('\n'); 46 | console.log(`Installing dependencies...`); 47 | execSync('npm install'); 48 | 49 | // remove extra files 50 | console.log('\n'); 51 | console.log(`Cleaning the workspace...`); 52 | execSync('npx rimraf ./.git'); // delete .git folder 53 | 54 | fs.unlinkSync(path.join(projectPath, 'bin', 'install.js')); 55 | fs.rmSync(path.join(projectPath, 'bin'), { recursive: true }); 56 | fs.unlinkSync(path.join(projectPath, 'events', 'sample-event.json')); 57 | fs.unlinkSync(path.join(projectPath, '.npmignore')); 58 | 59 | // end notes 60 | console.log('\n'); 61 | console.log('Installation is now complete!'); 62 | console.log("You're ready to go..."); 63 | console.log('\n'); 64 | console.log('We suggest that you start by typing:'); 65 | console.log(` cd ${projectName}`); 66 | console.log(); 67 | console.log('Enjoy your typescript lambda app, which will provide you some ready-made features!'); 68 | console.log('🎉 Tada...'); 69 | console.log(); 70 | } catch (error) { 71 | console.log(error); 72 | } 73 | } 74 | 75 | main(); 76 | -------------------------------------------------------------------------------- /events/sample-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "{\"message\": \"hello world\"}", 3 | "resource": "/{proxy+}", 4 | "path": "/path/to/resource", 5 | "httpMethod": "POST", 6 | "isBase64Encoded": false, 7 | "queryStringParameters": { 8 | "foo": "bar" 9 | }, 10 | "pathParameters": { 11 | "proxy": "/path/to/resource" 12 | }, 13 | "stageVariables": { 14 | "baz": "qux" 15 | }, 16 | "headers": { 17 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 18 | "Accept-Encoding": "gzip, deflate, sdch", 19 | "Accept-Language": "en-US,en;q=0.8", 20 | "Cache-Control": "max-age=0", 21 | "CloudFront-Forwarded-Proto": "https", 22 | "CloudFront-Is-Desktop-Viewer": "true", 23 | "CloudFront-Is-Mobile-Viewer": "false", 24 | "CloudFront-Is-SmartTV-Viewer": "false", 25 | "CloudFront-Is-Tablet-Viewer": "false", 26 | "CloudFront-Viewer-Country": "US", 27 | "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 28 | "Upgrade-Insecure-Requests": "1", 29 | "User-Agent": "Custom User Agent String", 30 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 31 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", 32 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 33 | "X-Forwarded-Port": "443", 34 | "X-Forwarded-Proto": "https" 35 | }, 36 | "requestContext": { 37 | "accountId": "123456789012", 38 | "resourceId": "123456", 39 | "stage": "prod", 40 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 41 | "requestTime": "09/Apr/2015:12:34:56 +0000", 42 | "requestTimeEpoch": 1428582896000, 43 | "identity": { 44 | "cognitoIdentityPoolId": null, 45 | "accountId": null, 46 | "cognitoIdentityId": null, 47 | "caller": null, 48 | "accessKey": null, 49 | "sourceIp": "127.0.0.1", 50 | "cognitoAuthenticationType": null, 51 | "cognitoAuthenticationProvider": null, 52 | "userArn": null, 53 | "userAgent": "Custom User Agent String", 54 | "user": null 55 | }, 56 | "path": "/prod/path/to/resource", 57 | "resourcePath": "/{proxy+}", 58 | "httpMethod": "POST", 59 | "apiId": "1234567890", 60 | "protocol": "HTTP/1.1" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | transform: { 8 | '^.+\\.ts?$': 'esbuild-jest', 9 | }, 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: 'coverage', 13 | coverageProvider: 'v8', 14 | testMatch: ['**/__tests__/unit/handlers/*.test.ts'], 15 | }; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-lambda-kit", 3 | "version": "1.0.4-beta", 4 | "description": "aws lambda kit for making backend with NodeJS v18, TypeScript & SAM CLI", 5 | "keywords": [ 6 | "ts-lambda-kit", 7 | "CloudFormation", 8 | "Amazon DynamoDB", 9 | "Amazon API Gateway", 10 | "Lambda Function", 11 | "TypeScript", 12 | "SAM CLI", 13 | "AWS CLi v2", 14 | "Node.js", 15 | "REST API", 16 | "Opinionated Architecture", 17 | "Serverless Computing", 18 | "AWS Lambda Backend", 19 | "ts-lambda", 20 | "ts-lambda-api", 21 | "lambda", 22 | "lambda-template", 23 | "lambda-api" 24 | ], 25 | "author": "Sazal Ahamed (https://sazal.vercel.app)", 26 | "license": "ISC", 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/DevSazal/ts-lambda-kit.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/DevSazal/ts-lambda-kit/issues" 33 | }, 34 | "homepage": "https://github.com/DevSazal/ts-lambda-kit#readme", 35 | "bin": { 36 | "ts-lambda-kit": "bin/install.js" 37 | }, 38 | "scripts": { 39 | "unit": "jest", 40 | "lint": "eslint '*.ts' --quiet --fix", 41 | "compile": "tsc", 42 | "test": "npm run compile && npm run unit" 43 | }, 44 | "dependencies": { 45 | "aws-sdk": "^2.799.0", 46 | "esbuild": "^0.14.54" 47 | }, 48 | "devDependencies": { 49 | "@types/aws-lambda": "^8.10.108", 50 | "@types/jest": "^29.2.0", 51 | "@types/node": "^18.11.9", 52 | "@typescript-eslint/eslint-plugin": "^5.10.2", 53 | "@typescript-eslint/parser": "^5.10.2", 54 | "colors": "^1.4.0", 55 | "esbuild-jest": "^0.5.0", 56 | "eslint": "^8.8.0", 57 | "eslint-config-prettier": "^8.3.0", 58 | "eslint-plugin-prettier": "^4.0.0", 59 | "husky": "^8.0.2", 60 | "jest": "^29.2.1", 61 | "prettier": "^2.5.1", 62 | "ts-node": "^10.9.1", 63 | "typescript": "^4.9.3" 64 | }, 65 | "husky": { 66 | "hooks": { 67 | "pre-commit": "make lint" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 4, 7 | }; 8 | -------------------------------------------------------------------------------- /samconfig.toml: -------------------------------------------------------------------------------- 1 | version = 0.1 2 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: > 4 | Managed stack with TS Lambda KIT, AWS SAM CLI & TypeScript 5 | 6 | # To know more, just visit https://www.npmjs.com/package/ts-lambda-kit 7 | # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst 8 | Globals: 9 | Function: 10 | Timeout: 3 11 | 12 | Resources: 13 | RestApiGateway: 14 | Type: AWS::Serverless::Api # More info about RestApi Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html 15 | Properties: 16 | StageName: v1 17 | CacheClusterEnabled: false 18 | # CacheClusterSize: '0.5' 19 | MethodSettings: 20 | - ResourcePath: /* 21 | HttpMethod: '*' 22 | CachingEnabled: true 23 | CacheTtlInSeconds: 300 24 | 25 | AWSLambdaFunctionPublicAPI: 26 | Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction 27 | Properties: 28 | CodeUri: src/handlers/ 29 | Handler: public-api@v1.lambdaHandler 30 | Runtime: nodejs18.x 31 | Architectures: 32 | - x86_64 33 | Events: 34 | ApiGatewayRoutePostUsers: 35 | Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api 36 | Properties: 37 | Path: /users 38 | Method: POST 39 | RestApiId: 40 | Ref: RestApiGateway 41 | ApiGatewayRouteGetUsers: 42 | Type: Api 43 | Properties: 44 | Path: /users 45 | Method: GET 46 | RestApiId: 47 | Ref: RestApiGateway 48 | ApiGatewayRouteGetUserById: 49 | Type: Api 50 | Properties: 51 | Path: /users/{id} 52 | Method: GET 53 | RestApiId: 54 | Ref: RestApiGateway 55 | ApiGatewayRoutePatchUser: 56 | Type: Api 57 | Properties: 58 | Path: /users/{id} 59 | Method: PATCH 60 | RestApiId: 61 | Ref: RestApiGateway 62 | ApiGatewayRouteDeleteUser: 63 | Type: Api 64 | Properties: 65 | Path: /users/{id} 66 | Method: DELETE 67 | RestApiId: 68 | Ref: RestApiGateway 69 | Metadata: # Manage esbuild properties 70 | BuildMethod: esbuild 71 | BuildProperties: 72 | Minify: true 73 | Target: "es2020" 74 | Sourcemap: true 75 | EntryPoints: 76 | - public-api@v1.ts 77 | 78 | Outputs: 79 | # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function 80 | # Find out more about other implicit resources you can reference within SAM 81 | # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api 82 | PublicAPI: 83 | Description: "API Gateway endpoint URL for v1 stage for PublicAPI function" 84 | Value: !Sub "https://${RestApiGateway}.execute-api.${AWS::Region}.amazonaws.com/v1/" 85 | AWSLambdaFunctionPublicAPI: 86 | Description: "PublicAPI Lambda Function ARN" 87 | Value: !GetAtt AWSLambdaFunctionPublicAPI.Arn 88 | AWSLambdaFunctionPublicAPIIamRole: 89 | Description: "Implicit IAM Role created for PublicAPI function" 90 | Value: !GetAtt AWSLambdaFunctionPublicAPIRole.Arn 91 | -------------------------------------------------------------------------------- /src/handlers/public-api@v1.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; 2 | import { response } from '../lib/HttpResponse'; 3 | 4 | /** 5 | * 6 | * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format 7 | * @param {Object} event - API Gateway Lambda Proxy Input Format 8 | * 9 | * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html 10 | * @returns {Object} object - API Gateway Lambda Proxy Output Format 11 | * 12 | */ 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | API Handler: AWS Lambda Function 17 | |-------------------------------------------------------------------------- 18 | | @apiVersion 1.0 19 | | 20 | | Here is where you can define all the endpoints for your application. 21 | | Enjoy building your API! 22 | | 23 | */ 24 | 25 | const main = async (event: APIGatewayProxyEvent): Promise => { 26 | const data = { 27 | ...JSON.parse(event.body || '{}'), 28 | ...event.queryStringParameters, 29 | ...event.pathParameters, 30 | }; 31 | 32 | const routeKey = `${event.httpMethod} ${event.resource}`; 33 | 34 | switch (routeKey) { 35 | case 'POST /users': 36 | return postUser(data as IRequestDataPostUser); 37 | 38 | case 'GET /users': 39 | return getUsers(); 40 | 41 | case 'GET /users/{id}': 42 | return getUserById(data as IRequestDataGetUserById); 43 | 44 | case 'PATCH /users/{id}': 45 | return patchUser(data as IRequestDataPatchUser); 46 | 47 | case 'DELETE /users/{id}': 48 | return deleteUser(data as IRequestDataDeleteUser); 49 | 50 | default: 51 | return response(404, 'resource not found!'); 52 | } 53 | 54 | // All log statements are written to AWS CloudWatch 55 | // console.info(`response from: ${event.path}`); 56 | }; 57 | 58 | enum EState { 59 | active = 'active', 60 | deleted = 'deleted', 61 | disabled = 'disabled', 62 | inactive = 'inactive', 63 | } 64 | 65 | // users request interface 66 | 67 | interface IRequestDataPostUser { 68 | id: number; 69 | firstName: string; 70 | lastName: string; 71 | state?: EState.active | EState.disabled; 72 | } 73 | 74 | interface IRequestDataGetUserById { 75 | id: number; 76 | } 77 | 78 | interface IRequestDataPatchUser { 79 | id: number; 80 | firstName: string; 81 | lastName: string; 82 | state?: EState.active | EState.disabled; 83 | } 84 | 85 | interface IRequestDataDeleteUser { 86 | id: number; 87 | } 88 | 89 | const postUser = async (request: IRequestDataPostUser) => { 90 | return response(201, `POST /v1/users accepted => hello ${request.firstName}`); 91 | }; 92 | 93 | const getUsers = async () => { 94 | return response(`GET /v1/users accepted`); 95 | }; 96 | 97 | const getUserById = async (request: IRequestDataGetUserById) => { 98 | return response({ message: `GET /v1/users/${request.id} accepted` }); 99 | }; 100 | 101 | const patchUser = async (request: IRequestDataPatchUser) => { 102 | return response(200, { message: `PATCH /v1/users/${request.id} accepted` }); 103 | }; 104 | 105 | const deleteUser = async (request: IRequestDataDeleteUser) => { 106 | return { 107 | statusCode: 200, 108 | body: JSON.stringify({ 109 | message: `DELETE /v1/users/${request.id} accepted`, 110 | }), 111 | }; 112 | }; 113 | 114 | export const lambdaHandler = main; 115 | -------------------------------------------------------------------------------- /src/lib/HttpResponse.ts: -------------------------------------------------------------------------------- 1 | import { APIGatewayProxyResult } from 'aws-lambda'; 2 | 3 | /** 4 | * Works for endpoint response with Lambda 5 | * by following Rest API or HTTP API integration Payload Format version 1.0 6 | * @param {Number} statusCode 7 | * @param {String | Object} body 8 | * @returns {Object} object - Output Format 9 | */ 10 | 11 | export const response = async ( 12 | param1: any, 13 | param2?: string | object, 14 | isBase64Encoded?: boolean | undefined, 15 | headers?: 16 | | { 17 | [header: string]: boolean | number | string; 18 | } 19 | | undefined, 20 | multiValueHeaders?: 21 | | { 22 | [header: string]: Array; 23 | } 24 | | undefined, 25 | ): Promise => { 26 | let statusCode: number; 27 | let body: string | object; 28 | 29 | if (typeof param1 === 'number' && param2 != null) { 30 | statusCode = param1; 31 | body = param2; 32 | } else { 33 | statusCode = 200; 34 | body = param1; 35 | } 36 | 37 | if (typeof body === 'string') body = { message: body }; 38 | if (headers == null) headers = {}; 39 | if (multiValueHeaders == null) multiValueHeaders = {}; 40 | 41 | return { 42 | statusCode, 43 | body: JSON.stringify(body), 44 | isBase64Encoded: false, 45 | headers, 46 | multiValueHeaders, 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /src/lib/helpers.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'crypto'; 2 | 3 | /** 4 | * To generate a random [RFC 4122](https://www.rfc-editor.org/rfc/rfc4122.txt) v4 UUID 5 | * @return cryptographic strong random value 6 | */ 7 | export const UUID = async (): Promise => { 8 | return randomUUID(); 9 | }; 10 | 11 | /** 12 | * To escape html special characters by converting string 13 | * @param {string} str - string value 14 | * @return {string} - safe string value 15 | */ 16 | export const escapeHtmlSpecialChars = async (str: string) => { 17 | const chars: { [key: string]: string } = { 18 | '&': '&', 19 | '<': '<', 20 | '>': '>', 21 | '"': '"', 22 | "'": ''', 23 | }; 24 | 25 | const matcher = new RegExp(`[${Object.keys(chars)}]`, 'g'); 26 | return str.replace(matcher, (s) => chars[s]); 27 | }; 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "strict": true, 5 | "preserveConstEnums": true, 6 | "noEmit": true, 7 | "sourceMap": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "noUnusedLocals": false, 11 | "noUnusedParameters": false, 12 | // "outDir": ".aws-sam/build", 13 | "rootDir": "src/", 14 | "esModuleInterop": true, 15 | "skipLibCheck": true, 16 | "forceConsistentCasingInFileNames": true 17 | }, 18 | "include": ["src/**/*"], 19 | "exclude": ["node_modules", "!node_modules/@types", "**/*.test.ts"] 20 | } 21 | --------------------------------------------------------------------------------