├── .env ├── .env.development ├── .eslintignore ├── .eslintrc ├── .github ├── FUNDING.yml └── workflows │ ├── CI.yml │ ├── release.yml │ └── update-deps.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── jest.config.js ├── lint-staged.config.js ├── package-lock.json ├── package.json ├── prisma └── schema.prisma ├── scripts ├── database.ts ├── deployDbProd.ts └── prisma.ts ├── serverless.yml ├── src ├── app.test.ts ├── app.ts └── handler.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | # Environment variables declared in this file are automatically made available to Prisma. 2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema 3 | 4 | # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. 5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 6 | 7 | DATABASE_URL=mongodb://127.0.0.1:27017,127.0.0.1:27018/modernmern?replicaSet=testset 8 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # Add your environment variables here 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Serverless 5 | .serverless 6 | 7 | # Webpack 8 | .webpack 9 | 10 | # Output 11 | dist 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Configuration for JavaScript files 3 | "extends": ["airbnb-base", "plugin:prettier/recommended"], 4 | "rules": { 5 | "prettier/prettier": [ 6 | "error", 7 | { 8 | "singleQuote": true, 9 | "endOfLine": "auto" 10 | } 11 | ] 12 | }, 13 | "overrides": [ 14 | // Configuration for TypeScript files 15 | { 16 | "files": ["**/*.ts", "**/*.tsx"], 17 | "plugins": ["@typescript-eslint", "unused-imports", "simple-import-sort"], 18 | "extends": [ 19 | "airbnb-base", 20 | "airbnb-typescript/base", 21 | "plugin:prettier/recommended" 22 | ], 23 | "parserOptions": { 24 | "project": "./tsconfig.json" 25 | }, 26 | "rules": { 27 | "prettier/prettier": [ 28 | "error", 29 | { 30 | "singleQuote": true, 31 | "endOfLine": "auto" 32 | } 33 | ], 34 | "import/extensions": [ 35 | "error", 36 | "ignorePackages", 37 | { 38 | "js": "never", 39 | "jsx": "never", 40 | "ts": "never", 41 | "tsx": "never", 42 | "": "never" 43 | } 44 | ], // Avoid missing file extension errors when using '@/' alias 45 | "@typescript-eslint/comma-dangle": "off", // Avoid conflict rule between Eslint and Prettier 46 | "@typescript-eslint/consistent-type-imports": "error", // Ensure `import type` is used when it's necessary 47 | "no-restricted-syntax": [ 48 | "error", 49 | "ForInStatement", 50 | "LabeledStatement", 51 | "WithStatement" 52 | ], // Overrides Airbnb configuration and enable no-restricted-syntax 53 | "import/prefer-default-export": "off", // Named export is easier to refactor automatically 54 | "simple-import-sort/imports": "error", // Import configuration for `eslint-plugin-simple-import-sort` 55 | "simple-import-sort/exports": "error", // Export configuration for `eslint-plugin-simple-import-sort` 56 | "import/order": "off", // Avoid conflict rule between `eslint-plugin-import` and `eslint-plugin-simple-import-sort` 57 | "@typescript-eslint/no-unused-vars": "off", 58 | "unused-imports/no-unused-imports": "error", 59 | "unused-imports/no-unused-vars": [ 60 | "error", 61 | { "argsIgnorePattern": "^_" } 62 | ] 63 | } 64 | }, 65 | // Configuration for testing 66 | { 67 | "files": ["**/*.test.ts"], 68 | "plugins": ["jest", "jest-formatting"], 69 | "extends": [ 70 | "plugin:jest/recommended", 71 | "plugin:jest-formatting/recommended" 72 | ], 73 | "rules": { 74 | "jest/no-mocks-import": "off" 75 | } 76 | } 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ixartz 2 | custom: 3 | ["https://donate.stripe.com/7sI5m5146ehfddm7tj", "https://modernmern.com"] 4 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | node-version: [18.x, 20.x] 14 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 15 | 16 | name: Build with ${{ matrix.node-version }} 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: "npm" 26 | - run: npm ci 27 | - run: npm run test:dev 28 | 29 | test: 30 | strategy: 31 | matrix: 32 | node-version: [18.x] 33 | 34 | name: Run all tests 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v3 39 | with: 40 | fetch-depth: 0 # Retrieve Git history, needed to verify commits 41 | - name: Use Node.js ${{ matrix.node-version }} 42 | uses: actions/setup-node@v3 43 | with: 44 | node-version: ${{ matrix.node-version }} 45 | cache: "npm" 46 | - run: npm ci 47 | 48 | - if: github.event_name == 'pull_request' 49 | name: Validate all commits from PR 50 | run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose 51 | 52 | - name: Linter 53 | run: npm run lint 54 | 55 | - name: Type checking 56 | run: npm run check-types 57 | 58 | - name: Run unit tests 59 | run: npm run test 60 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["CI"] 6 | types: 7 | - completed 8 | branches: 9 | - main 10 | 11 | jobs: 12 | release: 13 | strategy: 14 | matrix: 15 | node-version: [18.x] 16 | 17 | name: Create a new release 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | fetch-depth: 0 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: "npm" 29 | - run: HUSKY=0 npm ci 30 | 31 | - name: Release 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | run: npx semantic-release 35 | -------------------------------------------------------------------------------- /.github/workflows/update-deps.yml: -------------------------------------------------------------------------------- 1 | name: Update dependencies 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 0 1 * *" 7 | 8 | jobs: 9 | update: 10 | strategy: 11 | matrix: 12 | node-version: [18.x] 13 | 14 | name: Update all dependencies 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | cache: "npm" 24 | - run: npm ci 25 | 26 | - run: npx npm-check-updates -u # Update dependencies 27 | - run: rm -Rf node_modules package-lock.json 28 | - run: npm install 29 | - name: Create Pull Request 30 | uses: peter-evans/create-pull-request@v4 31 | with: 32 | commit-message: "build: update dependencies to the latest version" 33 | title: Update dependencies to the latest version 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless 6 | .serverless 7 | 8 | # Dynamodb 9 | .dynamodb 10 | 11 | # Webpack 12 | .webpack 13 | .esbuild 14 | 15 | # Output 16 | dist 17 | 18 | # Dotenv files 19 | .env*.local 20 | 21 | # Local folder 22 | local 23 | 24 | # testing 25 | coverage 26 | 27 | # Misc 28 | .DS_Store 29 | *.pem 30 | Thumbs.db 31 | 32 | # Error logs 33 | npm-debug.log* 34 | pnpm-debug.log* 35 | yarn-debug.log* 36 | yarn-error.log* 37 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | cd "$(dirname "$0")/.." && npx --no -- commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | # Disable concurent to run `check-types` after ESLint in lint-staged 5 | cd "$(dirname "$0")/.." && npx lint-staged --concurrent false 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "mikestead.dotenv", 6 | "Orta.vscode-jest", 7 | "prisma.prisma", 8 | "mongodb.mongodb-vscode", 9 | "yoavbls.pretty-ts-errors" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Severless debug", 11 | "program": "${workspaceFolder}/node_modules/.bin/sls", 12 | "args": ["offline", "start", "--stage", "offline"], 13 | "env": { "NODE_ENV": "development" }, 14 | "autoAttachChildProcesses": true, 15 | "sourceMaps": true, 16 | "skipFiles": ["/**"], 17 | "console": "integratedTerminal", 18 | "outFiles": ["${workspaceFolder}/.esbuild/.build/**/*.js"] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.detectIndentation": false, 4 | "search.exclude": { 5 | "package-lock.json": true 6 | }, 7 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 8 | "editor.formatOnSave": false, 9 | "editor.codeActionsOnSave": [ 10 | "source.addMissingImports", 11 | "source.fixAll.eslint" 12 | ], 13 | "jest.autoRun": { 14 | "watch": false // Start the jest with the watch flag 15 | // "onStartup": ["all-tests"] // Run all tests upon project launch 16 | }, 17 | "jest.showCoverageOnLoad": true, // Show code coverage when the project is launched 18 | "jest.autoRevealOutput": "on-exec-error", // Don't automatically open test explorer terminal on launch 19 | // Multiple language settings for json and jsonc files 20 | "[json][jsonc][yaml]": { 21 | "editor.formatOnSave": true, 22 | "editor.defaultFormatter": "esbenp.prettier-vscode" 23 | }, 24 | "[prisma]": { 25 | "editor.formatOnSave": true, 26 | "editor.defaultFormatter": "Prisma.prisma" 27 | }, 28 | "prettier.ignorePath": ".gitignore" // Don't run prettier for files listed in .gitignore 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Project wide type checking with TypeScript", 8 | "type": "npm", 9 | "script": "check-types", 10 | "problemMatcher": ["$tsc"], 11 | "group": { 12 | "kind": "build", 13 | "isDefault": true 14 | }, 15 | "presentation": { 16 | "clear": true, 17 | "reveal": "never" 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.2.1](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/compare/v1.2.0...v1.2.1) (2023-07-23) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * resolve no such option -c during deployment ([632f93e](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/632f93e1acebbe4eec86a12f86b6092527ce538f)) 7 | 8 | # [1.2.0](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/compare/v1.1.0...v1.2.0) (2023-05-05) 9 | 10 | 11 | ### Features 12 | 13 | * add pretty-ts-errors extension in vscode ([55875be](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/55875be5f6a8e2daa85c27384f6dfd1199b2ce0d)) 14 | 15 | 16 | ### Reverts 17 | 18 | * remove node.js version 20 support ([0047634](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/004763445d655bf3a24f3b97eeeb50fb9f2e43a7)) 19 | 20 | # [1.1.0](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/compare/v1.0.0...v1.1.0) (2023-04-07) 21 | 22 | 23 | ### Features 24 | 25 | * add .esbuild folder in gitignore file ([b6c9316](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/b6c9316ee0f1fc04781947ffbb50b3a0c693cda8)) 26 | * enable vscode debugger with serverless-esbuild ([f641b94](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/f641b94f7526061f02beb85f61cb764ea784d9e4)) 27 | * migrate to serverless-esbuild from serverless-bundle ([d4887ef](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/d4887efb122311851614c20f337a8892e6a1ecfd)) 28 | * remove .esbuild folder ([dfe1991](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/dfe1991f12edd02f9684fd728ccbf2817e919f78)) 29 | * update package-lock.json file ([4daef9b](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/4daef9b545fa6f920d98f4768177d9023833355f)) 30 | 31 | 32 | ### Reverts 33 | 34 | * add support for all Node.js 14+, too restrictive with only Node.js 18+ ([56c988e](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/56c988ec898e381fd37827bdf3ae164e2f58d279)) 35 | 36 | # 1.0.0 (2022-12-22) 37 | 38 | 39 | ### Features 40 | 41 | * add commit script in package.json, making easier to commit ([5cfe88e](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/5cfe88e87827dcdbba34991b161ebde265e2527c)) 42 | * add example on how to save into MongoDB with Prisma ([73b35d4](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/73b35d40a99f0ebadc8856e8e2525b95ac83d588)) 43 | * add Prisma ORM ([67c6fc5](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/67c6fc5edaee518b192e04dce9eb38fb8227f390)) 44 | * add support for alias with tsconfig, eslint configuration and jest ([9de953e](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/9de953ef8d3eebba3f8554934f90707c0d92b36f)) 45 | * use @swc/jest instead of ts-jest ([6bf059d](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/6bf059d7c8b35979b76bfeaea65fdac67aeb7ee6)) 46 | 47 | 48 | ### Reverts 49 | 50 | * aws lambda URLs which currently not support by serverless-http plugin ([c060cdf](https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript/commit/c060cdf7afbc0384454bb8d7d350b945892ea54c)) 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rem W. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serverless Boilerplate with Serverless Framework 3, ExpressJS, TypeScript, Prisma and MongoDB [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Follow%20%40Ixartz)](https://twitter.com/ixartz) 2 | 3 |

4 | Serverless Boilerplate Banner 5 |

6 | 7 | 🚀 Serverless Boilerplate is starter code for your backend and REST API based on Serverless framework with Express JS, TypeScript, Prisma and MongoDB. ⚡️ Made with Serverless framework, Live reload, Offline support, ExpressJS, TypeScript, ESLint, Prettier, Husky, Lint-Staged, Jest, Commitlint, Dotenv, VSCode. 8 | 9 | Clone this project and use it to create your own backend. 10 | 11 | ### Features 12 | 13 | Developer experience first: 14 | 15 | - 🔥 [Serverless framework](https://www.serverless.com) 16 | - ⚡️ [ExpressJS](http://expressjs.com) 17 | - ✅ Type checking [TypeScript](https://www.typescriptlang.org) with strict mode 18 | - 📚 ORM with [Prisma](https://www.prisma.io) 19 | - 💖 Database with [MongoDB](https://www.mongodb.com/) with in-memory database for local development 20 | - 📏 Linter with [ESLint](https://eslint.org) with Airbnb configuration 21 | - 💖 Code Formatter with [Prettier](https://prettier.io) 22 | - 🦊 Husky for Git Hooks 23 | - 🚫 Lint-staged for running linters on Git staged files 24 | - 🚓 Lint git commit with Commitlint 25 | - 📓 Write standard compliant commit messages with Commitizen 26 | - 🦺 Unit testing with Jest and Supertest for integration testing 27 | - 👷 Run tests on pull request with GitHub Actions 28 | - 🎁 Automatic changelog generation with Semantic Release 29 | - 💡 Absolute Imports using @ prefix 30 | - 🗂 VSCode configuration: Debug, Settings, Tasks and extension for ESLint, Prettier, TypeScript, Jest 31 | - 📖 Local support with Serverless Offline 32 | - ⚙️ Environment variable with Serverless Dotenv 33 | - 🎉 Fast bundler with esbuild 34 | - ✨ HTTP Api instead of API gateway for cost optimization 35 | - 💨 Live reload 36 | 37 | ### Philosophy 38 | 39 | - Minimal code 40 | - 🚀 Production-ready 41 | 42 | ### Node.js SaaS Boilerplate - MERN Stack with Serverless 43 | 44 | Build your SaaS product faster with [Node.js SaaS Boilerplate](https://modernmern.com). 45 | 46 | [![Node.js React SaaS Boilerplate](https://creativedesignsguru.com/assets/images/themes/nodejs-saas-boilerplate-mern-starter-kit.jpg)](https://modernmern.com) 47 | 48 | ### Premium Themes 49 | 50 | | [Green Nextjs Landing Page Template](https://creativedesignsguru.com/landing-green-modern-nextjs-theme/) | [Purple Saas Nextjs Theme](https://creativedesignsguru.com/landing-purple-modern-react-theme/) | 51 | | --- | --- | 52 | | [![Green Nextjs Landing Page Template](https://creativedesignsguru.com/assets/images/themes/landing-green-modern-nextjs-theme-xs.png)](https://creativedesignsguru.com/landing-green-modern-nextjs-theme/) | [![Blue Landing Page Nextjs Theme](https://creativedesignsguru.com/assets/images/themes/landing-blue-modern-nextjs-theme-xs.png)](https://creativedesignsguru.com/landing-blue-modern-react-theme/) | 53 | 54 | Find more [Nextjs Templates](https://creativedesignsguru.com/category/nextjs/). 55 | ### Requirements 56 | 57 | - Node.js 16+ and npm 58 | 59 | ### Getting started 60 | 61 | Run the following command on your local environment: 62 | 63 | ``` 64 | git clone --depth=1 https://github.com/ixartz/Serverless-Boilerplate-Express-TypeScript.git my-project-name 65 | cd my-project-name 66 | npm install 67 | ``` 68 | 69 | Then, you can run locally in development mode with live reload: 70 | 71 | ``` 72 | npm run dev 73 | ``` 74 | 75 | The local server is now listening at http://localhost:4000 76 | 77 | ### Deploy to production 78 | 79 | You can deploy to production with the following command: 80 | 81 | ``` 82 | npm run deploy-prod 83 | ``` 84 | 85 | ### VSCode information (optional) 86 | 87 | If you are VSCode users, you can have a better integration with VSCode by installing the suggested extension in `.vscode/extension.json`. The starter code comes up with Settings for a seamless integration with VSCode. The Debug configuration is also provided for frontend and backend debugging experience. 88 | 89 | With the plugins installed on your VSCode, ESLint and Prettier can automatically fix the code and show you the errors. Same goes for testing, you can install VSCode Jest extension to automatically run your tests and it also show the code coverage in context. 90 | 91 | Pro tips: if you need a project wide type checking with TypeScript, you can run a build with Cmd + Shift + B on Mac. 92 | 93 | ### Contributions 94 | 95 | Everyone is welcome to contribute to this project. Feel free to open an issue if you have question or found a bug. 96 | 97 | ### License 98 | 99 | Licensed under the MIT License, Copyright © 2022 100 | 101 | See [LICENSE](LICENSE) for more information. 102 | 103 | --- 104 | 105 | Made with ♥ by [CreativeDesignsGuru](https://creativedesignsguru.com) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Follow%20%40Ixartz)](https://twitter.com/ixartz) 106 | 107 | [![Node.js React SaaS Boilerplate](https://creativedesignsguru.com/assets/images/themes/nodejs-saas-boilerplate-mern-starter-kit.jpg)](https://modernmern.com) 108 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: ['**/*.test.{js,ts}'], 3 | transform: { 4 | '^.+\\.[jt]s$': ['@swc/jest'], 5 | }, 6 | moduleNameMapper: { 7 | // Handle module aliases (this will be automatically configured for you soon) 8 | '^@/(.*)$': '/src/$1', 9 | 10 | '^__mocks__/(.*)$': '/__mocks__/$1', 11 | }, 12 | clearMocks: true, 13 | collectCoverage: true, 14 | collectCoverageFrom: [ 15 | './src/**/*.{js,ts}', 16 | '!src/handler.ts', 17 | '!**/*.d.ts', 18 | '!**/node_modules/**', 19 | ], 20 | coverageThreshold: { 21 | global: { 22 | branches: 30, 23 | functions: 30, 24 | lines: 30, 25 | statements: 30, 26 | }, 27 | }, 28 | testPathIgnorePatterns: ['/node_modules/', '/.serverless/'], 29 | }; 30 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{js,jsx,ts,tsx}': ['eslint --fix', 'eslint'], 3 | '**/*.ts?(x)': () => 'npm run check-types', 4 | '*.json': ['prettier --write'], 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-boilerplate-express-typescript", 3 | "version": "1.2.1", 4 | "description": "Serverless framework ExpressJS TypeScript", 5 | "scripts": { 6 | "dev:db": "tsx scripts/database.ts", 7 | "dev:server": "cross-env NODE_ENV=development NODE_OPTIONS=--dns-result-order=ipv4first sls offline start --stage offline", 8 | "dev": "run-p dev:*", 9 | "tail-log": "sls logs -f app -t", 10 | "deploy:db": "dotenv -e .env.production -c -- tsx scripts/deployDbProd.ts", 11 | "deploy:app": "cross-env NODE_ENV=production sls deploy --stage prod", 12 | "deploy-prod": "run-s deploy:*", 13 | "remove-prod": "cross-env NODE_ENV=production sls remove --stage prod", 14 | "clean": "rimraf dist .esbuild .serverless", 15 | "lint": "eslint .", 16 | "format": "eslint --fix . && prettier '**/*.{json,yaml}' --write --ignore-path .gitignore", 17 | "check-types": "tsc --noEmit --pretty", 18 | "commit": "cz", 19 | "test": "jest", 20 | "test:dev": "start-server-and-test dev http://127.0.0.1:4000 'curl --head http://127.0.0.1:4000'", 21 | "db:push": "prisma db push", 22 | "prepare": "husky install", 23 | "postinstall": "prisma generate" 24 | }, 25 | "dependencies": { 26 | "@prisma/client": "^5.11.0", 27 | "express": "^4.19.1", 28 | "express-async-errors": "^3.1.1", 29 | "helmet": "^7.1.0", 30 | "serverless-http": "^3.2.0" 31 | }, 32 | "devDependencies": { 33 | "@commitlint/cli": "^19.2.1", 34 | "@commitlint/config-conventional": "^19.1.0", 35 | "@commitlint/cz-commitlint": "^19.2.0", 36 | "@semantic-release/changelog": "^6.0.3", 37 | "@semantic-release/git": "^10.0.1", 38 | "@swc/core": "^1.4.8", 39 | "@swc/jest": "^0.2.36", 40 | "@types/cross-spawn": "^6.0.6", 41 | "@types/express": "^4.17.21", 42 | "@types/jest": "^29.5.12", 43 | "@types/supertest": "^6.0.2", 44 | "@typescript-eslint/eslint-plugin": "^7.3.1", 45 | "@typescript-eslint/parser": "^7.3.1", 46 | "commitizen": "^4.3.0", 47 | "cross-env": "^7.0.3", 48 | "cross-spawn": "^7.0.3", 49 | "dotenv-cli": "^7.4.1", 50 | "esbuild": "~0.16.17", 51 | "eslint": "^8.57.0", 52 | "eslint-config-airbnb-base": "^15.0.0", 53 | "eslint-config-airbnb-typescript": "^18.0.0", 54 | "eslint-config-prettier": "^9.1.0", 55 | "eslint-plugin-import": "^2.29.1", 56 | "eslint-plugin-jest": "^27.9.0", 57 | "eslint-plugin-jest-formatting": "^3.1.0", 58 | "eslint-plugin-prettier": "^5.1.3", 59 | "eslint-plugin-simple-import-sort": "^12.0.0", 60 | "eslint-plugin-unused-imports": "^3.1.0", 61 | "husky": "^9.0.11", 62 | "jest": "^29.7.0", 63 | "lint-staged": "^15.2.2", 64 | "mongodb-memory-server": "^9.1.7", 65 | "npm-run-all": "^4.1.5", 66 | "p-retry": "^4.6.2", 67 | "prettier": "^3.2.5", 68 | "prisma": "^5.11.0", 69 | "rimraf": "^5.0.5", 70 | "semantic-release": "^22.0.12", 71 | "serverless": "^3.38.0", 72 | "serverless-dotenv-plugin": "^6.0.0", 73 | "serverless-esbuild": "^1.52.1", 74 | "serverless-offline": "^13.3.3", 75 | "start-server-and-test": "^2.0.3", 76 | "supertest": "^6.3.4", 77 | "tsconfig-paths": "^4.2.0", 78 | "tsx": "^4.7.1", 79 | "typescript": "^5.4.3" 80 | }, 81 | "config": { 82 | "commitizen": { 83 | "path": "@commitlint/cz-commitlint" 84 | } 85 | }, 86 | "release": { 87 | "branches": [ 88 | "main" 89 | ], 90 | "plugins": [ 91 | "@semantic-release/commit-analyzer", 92 | "@semantic-release/release-notes-generator", 93 | "@semantic-release/changelog", 94 | [ 95 | "@semantic-release/npm", 96 | { 97 | "npmPublish": false 98 | } 99 | ], 100 | "@semantic-release/git", 101 | "@semantic-release/github" 102 | ] 103 | }, 104 | "author": "Ixartz (https://github.com/ixartz)" 105 | } 106 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | binaryTargets = ["native", "rhel-openssl-1.0.x"] 7 | } 8 | 9 | datasource db { 10 | provider = "mongodb" 11 | url = env("DATABASE_URL") 12 | } 13 | 14 | model User { 15 | id String @id @default(auto()) @map("_id") @db.ObjectId 16 | email String @unique 17 | } 18 | -------------------------------------------------------------------------------- /scripts/database.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies,no-console */ 2 | import { MongoMemoryReplSet } from 'mongodb-memory-server'; 3 | import pRetry from 'p-retry'; 4 | 5 | import { prismaDbPush } from './prisma'; 6 | 7 | let mongodb: MongoMemoryReplSet | null = null; 8 | 9 | (async () => { 10 | mongodb = new MongoMemoryReplSet({ 11 | instanceOpts: [ 12 | { 13 | port: 27017, 14 | storageEngine: 'wiredTiger', 15 | }, 16 | { 17 | port: 27018, 18 | storageEngine: 'wiredTiger', 19 | }, 20 | ], 21 | }); 22 | 23 | await mongodb.start(); 24 | 25 | process.env.DATABASE_URL = mongodb 26 | .getUri() 27 | .replace('/?replicaSet=', `/modernmern?replicaSet=`); 28 | 29 | await pRetry(prismaDbPush, { retries: 5 }); 30 | 31 | console.log(`MongoDB ready - endpoint: ${mongodb.getUri()}`); 32 | })(); 33 | 34 | process.on('SIGINT', () => { 35 | if (mongodb) { 36 | mongodb.stop(); 37 | } 38 | 39 | process.exit(); 40 | }); 41 | -------------------------------------------------------------------------------- /scripts/deployDbProd.ts: -------------------------------------------------------------------------------- 1 | import { prismaDbPush } from './prisma'; 2 | 3 | (async () => { 4 | await prismaDbPush(); 5 | })(); 6 | -------------------------------------------------------------------------------- /scripts/prisma.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { spawn as spawnCb } from 'cross-spawn'; 3 | import { promisify } from 'util'; 4 | 5 | const spawn = promisify(spawnCb); 6 | 7 | export const prismaDbPush = () => { 8 | return spawn('prisma', ['db', 'push'], { 9 | stdio: 'inherit', 10 | env: { 11 | ...process.env, 12 | }, 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-boilerplate 2 | 3 | custom: 4 | # Our stage is based on what is passed in when running serverless 5 | # commands. Or falls back to what we have set in the provider section. 6 | stage: ${opt:stage, 'offline'} 7 | region: ${opt:region, 'us-east-1'} 8 | prismaEngine: 9 | offline: "node_modules/.prisma/client/*.node" 10 | staging: "node_modules/.prisma/client/libquery_engine-rhel*" 11 | prod: "node_modules/.prisma/client/libquery_engine-rhel*" 12 | 13 | esbuild: 14 | platform: "node" 15 | target: node16 16 | sourcemap: linked 17 | serverless-offline: 18 | httpPort: 4000 19 | noPrependStageInUrl: true 20 | reloadHandler: true 21 | 22 | provider: 23 | name: aws 24 | stage: ${self:custom.stage} 25 | region: ${self:custom.region} 26 | runtime: nodejs16.x 27 | memorySize: 512 28 | timeout: 10 29 | logRetentionInDays: 90 30 | logs: 31 | httpApi: true 32 | httpApi: 33 | # metrics: true # Enable if you need 34 | cors: true 35 | 36 | functions: 37 | app: 38 | handler: src/handler.handler 39 | # reservedConcurrency: 100 40 | events: 41 | - httpApi: 42 | path: "/{proxy+}" 43 | method: "*" 44 | 45 | package: 46 | patterns: 47 | - "!**/*.test.ts" 48 | - ${self:custom.prismaEngine.${self:custom.stage}} 49 | - "node_modules/.prisma/client/schema.prisma" 50 | individually: true 51 | 52 | plugins: 53 | - serverless-dotenv-plugin 54 | - serverless-esbuild 55 | - serverless-offline 56 | -------------------------------------------------------------------------------- /src/app.test.ts: -------------------------------------------------------------------------------- 1 | import supertest from 'supertest'; 2 | 3 | import { app } from './app'; 4 | 5 | describe('Express app', () => { 6 | describe('Routing', () => { 7 | it('should return `Hello world` when GET index', async () => { 8 | const response = await supertest(app).get('/'); 9 | 10 | expect(response.statusCode).toEqual(200); 11 | expect(response.body.msg).toEqual('Hello World'); 12 | }); 13 | 14 | it('should return `NOT FOUND` when GET a not found route', async () => { 15 | const response = await supertest(app).get('/random-page'); 16 | 17 | expect(response.statusCode).toEqual(404); 18 | expect(response.body.error).toEqual('NOT FOUND'); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import 'express-async-errors'; 2 | 3 | import { PrismaClient } from '@prisma/client'; 4 | import express, { json } from 'express'; 5 | import helmet from 'helmet'; 6 | 7 | const app = express(); 8 | app.use(json()); 9 | app.use(helmet()); 10 | 11 | const prisma = new PrismaClient(); 12 | 13 | app.get('/', (_, res) => { 14 | res.json({ 15 | msg: 'Hello World', 16 | }); 17 | }); 18 | 19 | app.get('/prisma', async (_, res) => { 20 | await prisma.user.create({ 21 | data: { 22 | email: 'random@example.com', 23 | }, 24 | }); 25 | 26 | res.json({ 27 | msg: 'Add a new unique user without duplicate', 28 | }); 29 | }); 30 | 31 | app.use((_, res, _2) => { 32 | res.status(404).json({ error: 'NOT FOUND' }); 33 | }); 34 | 35 | export { app }; 36 | -------------------------------------------------------------------------------- /src/handler.ts: -------------------------------------------------------------------------------- 1 | import serverlessHttp from 'serverless-http'; 2 | 3 | import { app } from './app'; 4 | 5 | export const handler = serverlessHttp(app); 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2017"], 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "removeComments": true, 8 | "preserveConstEnums": true, 9 | "strict": true, 10 | "alwaysStrict": true, 11 | "strictNullChecks": true, 12 | "noUncheckedIndexedAccess": true, 13 | 14 | "noImplicitAny": true, 15 | "noImplicitReturns": true, 16 | "noImplicitThis": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "allowUnreachableCode": false, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | "target": "es2017", 23 | "outDir": "dist", 24 | "declaration": true, 25 | "sourceMap": true, 26 | 27 | "esModuleInterop": true, 28 | "allowSyntheticDefaultImports": true, 29 | "allowJs": false, 30 | "skipLibCheck": true, 31 | "forceConsistentCasingInFileNames": true, 32 | 33 | "baseUrl": ".", 34 | "paths": { 35 | "@/*": ["./src/*"] 36 | } 37 | }, 38 | "exclude": ["./dist/**/*", "./node_modules/**/*"] 39 | } 40 | --------------------------------------------------------------------------------