├── .commitlintrc ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── codeql │ └── codeql-configuration.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── deploy-documents.yml │ └── update-package-lock.yaml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg ├── pre-commit └── pre-push ├── .lintstagedrc ├── .prettierignore ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── config └── env │ ├── .env │ ├── .env.example │ └── test.env ├── docker ├── certs │ ├── private-key-without-passphrase.pem │ └── public-cert-without-passphrase.pem ├── docker-compose.yml ├── init.sql └── nginx.conf ├── nest-cli.json ├── package.json ├── sonar-project.properties ├── src ├── app.controller.ts ├── app.module.ts ├── app.service.ts ├── config │ ├── app.ts │ ├── authentication.ts │ ├── crypto.ts │ ├── database.ts │ ├── index.ts │ ├── mailer.ts │ ├── services.ts │ └── settings.ts ├── core │ ├── auth │ │ ├── auth.controller.ts │ │ ├── auth.module.ts │ │ ├── auth.service.ts │ │ ├── jwt.auth.guard.ts │ │ └── jwt.strategy.ts │ ├── bootstrap.ts │ ├── compression │ │ └── compression.ts │ ├── cors.config.ts │ ├── crypto │ │ └── crypto.ts │ ├── db │ │ ├── database.module.ts │ │ └── typeorm.config.ts │ ├── hashing │ │ └── hashing.ts │ ├── index.ts │ ├── logger │ │ ├── AppLogger.ts │ │ └── index.ts │ ├── middleware │ │ ├── ErrorHandler.ts │ │ ├── RequestHandler.ts │ │ ├── ResponseHandler.ts │ │ └── index.ts │ ├── module.ts │ └── providers.ts ├── feature │ └── users │ │ ├── constants │ │ └── api.response.dto.ts │ │ ├── dto │ │ ├── create.user.dto.ts │ │ ├── update.user.dto.ts │ │ └── validate.user.dto.ts │ │ ├── entities │ │ └── user.entity.ts │ │ ├── repository │ │ ├── db │ │ │ └── user.repository.ts │ │ └── user.repository.ts │ │ ├── services │ │ └── users.service.ts │ │ ├── users.controller.ts │ │ └── users.module.ts ├── main.ts ├── shared │ └── mailer │ │ ├── email-templates │ │ ├── neosoft_logo.png │ │ ├── nest_logo.png │ │ └── sample.pug │ │ ├── mailer-interfaces │ │ ├── index.ts │ │ └── interfaces.ts │ │ ├── mailer.module.ts │ │ └── mailer.service.ts └── swagger │ └── index.ts ├── test ├── e2e │ └── app.e2e-spec.ts ├── mock │ ├── generate-token.stub.ts │ ├── user.stub.ts │ ├── user.update.stub.ts │ └── users.response.ts └── unit │ ├── authentication │ └── auth.controller.spec.ts │ ├── components │ ├── users.controller.spec.ts │ └── users.service.spec.ts │ ├── config │ ├── app.spec.ts │ ├── crypto.spec.ts │ └── db.spec.ts │ └── core │ ├── crypto.spec.ts │ ├── guards │ └── module │ │ └── core-test.module.ts │ ├── logger.spec.ts │ ├── mailer.spec.ts │ └── typeorm.spec.ts ├── tsconfig-paths-bootstrap.js ├── tsconfig.build.json ├── tsconfig.json └── wiki ├── docs ├── contribution.md ├── contribution │ ├── bug-reports.md │ ├── feature-requests.md │ └── pull-requests.md ├── coverage.md ├── environment │ ├── authentication.env.md │ ├── crypto.env.md │ ├── database.env.md │ ├── email.env.md │ └── environment.md ├── miscellaneous.md ├── miscellaneous │ ├── clean-docker.md │ ├── git-commits.md │ ├── installation-pretteri-husky-lint.md │ └── known-issues.md ├── modules.md ├── modules │ ├── authentication.md │ ├── compression.md │ ├── cors.md │ ├── crypto.md │ ├── database.md │ ├── database.migration.md │ ├── logger.md │ ├── mailer.md │ ├── pattern.md │ ├── request-response.md │ ├── reverse_proxy.md │ ├── ssl.md │ └── swagger.md ├── summary.json ├── trainings.md └── trainings │ ├── git.md │ ├── nestjs.md │ └── nodejs.md ├── images ├── CORS-console-result.PNG ├── CORS-response-headers.PNG ├── Inversion-of-control.jpg ├── basic-nestJS-lifecycle.png ├── code-flow.jpg ├── compressed.PNG ├── database-config.png ├── error-response.png ├── folder-structure.png ├── logger-function.png ├── meta-response.png ├── nestjs-request-life-cycle.png ├── no-content-response.png ├── nosql-query-injection.png ├── pem-pass.png ├── rate-limit-output.png ├── ssl-command.png ├── success-response.png ├── terminal-ssl.png └── uncompressed.PNG └── roadmaps └── nestjs-roadmap.png /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # /node_modules/* in the project root is ignored by default 2 | # build artefacts 3 | dist/* 4 | coverage/* 5 | # data definition files 6 | **/*.d.ts 7 | # 3rd party libs 8 | /src/public/ 9 | # custom definition files 10 | /src/types/ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin', 'import'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'plugin:prettier/recommended', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | ignorePatterns: ['.eslintrc.js'], 20 | rules: { 21 | '@typescript-eslint/interface-name-prefix': 'off', 22 | '@typescript-eslint/explicit-function-return-type': 'off', 23 | '@typescript-eslint/explicit-module-boundary-types': 'off', 24 | '@typescript-eslint/no-explicit-any': 'off', 25 | 'import/order': [ 26 | 'error', 27 | { 28 | pathGroups: [ 29 | { 30 | pattern: '@nest/**', 31 | group: 'external', 32 | position: 'before', 33 | }, 34 | { 35 | pattern: '@app/**', 36 | group: 'internal', 37 | position: 'after', 38 | }, 39 | ], 40 | pathGroupsExcludedImportTypes: ['internal'], 41 | 'newlines-between': 'always', 42 | groups: ['builtin', 'external', 'internal', 'unknown', 'parent', 'sibling', 'index', 'object', 'type'], 43 | alphabetize: { 44 | order: 'asc', 45 | caseInsensitive: true, 46 | }, 47 | }, 48 | ], 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/codeql/codeql-configuration.yml: -------------------------------------------------------------------------------- 1 | name : CodeQL Configuration 2 | 3 | paths: 4 | - './src' -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: npm 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | open-pull-requests-limit: 10 12 | target-branch: "dev" 13 | # Increase the version requirements 14 | # only when required 15 | versioning-strategy: increase-if-necessary 16 | labels: 17 | - "dependencies" 18 | - "npm" -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## PR Checklist 2 | Please check if your PR fulfills the following requirements: 3 | 4 | - [ ] The commit message follows our guidelines: https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/CONTRIBUTING.md 5 | - [ ] Tests for the changes have been added (for bug fixes / features) 6 | - [ ] Docs have been added / updated (for bug fixes / features) 7 | 8 | 9 | ## PR Type 10 | What kind of change does this PR introduce? 11 | 12 | 13 | - [ ] Bugfix 14 | - [ ] Feature 15 | - [ ] Code style update (formatting, local variables) 16 | - [ ] Refactoring (no functional changes, no api changes) 17 | - [ ] Build related changes 18 | - [ ] CI related changes 19 | - [ ] Other... Please describe: 20 | 21 | ## What is the current behavior? 22 | 23 | 24 | Issue Number: N/A 25 | 26 | 27 | ## What is the new behavior? 28 | 29 | 30 | ## Does this PR introduce a breaking change? 31 | - [ ] Yes 32 | - [ ] No 33 | 34 | 35 | 36 | 37 | ## Other information -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - dev 7 | - release-* 8 | pull_request: 9 | types: [opened, synchronize, reopened] 10 | branches: 11 | - main 12 | - release-* 13 | 14 | jobs: 15 | CI: 16 | strategy: 17 | matrix: 18 | # node-version: [10.x, 12.x, 14.x] 19 | node-version: [14.x] 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout Code 23 | uses: actions/checkout@v3 24 | - name: Set up nodejs version ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3.2.0 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - name: Set up MySQL Database 29 | uses: mirromutth/mysql-action@v1.1 30 | with: 31 | # host port: 3800 # Optional, default value is 3306. The port of host 32 | # container port: 3307 # Optional, default value is 3306. The port of container 33 | character set server: 'utf8' # Optional, default value is 'utf8mb4'. The '--character-set-server' option for mysqld 34 | collation server: 'utf8_general_ci' # Optional, default value is 'utf8mb4_general_ci'. The '--collation-server' option for mysqld 35 | mysql version: '8.0' # Optional, default value is "latest". The version of the MySQL 36 | mysql database: 'rest_api' # Optional, default value is "test". The specified database which will be create 37 | mysql root password: root # Required if "mysql user" is empty, default is empty. The root superuser password 38 | mysql user: 'user' # Required if "mysql root password" is empty, default is empty. The superuser for the specified database. Can use secrets, too 39 | mysql password: root # Required if "mysql user" exists. The password for the "mysql user" 40 | - name: Install package 41 | run: npm install 42 | - name: Linter 43 | run: npm run lint 44 | - name: Build 45 | run: npm run build --if-present 46 | - name: Test 47 | run: npm run test 48 | - name: SonarCloud Scan 49 | uses: sonarsource/sonarcloud-github-action@master 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: 17 | - main 18 | - dev 19 | - release-* 20 | pull_request: 21 | # The branches below must be a subset of the branches above 22 | branches: 23 | - main 24 | - dev 25 | - release-* 26 | schedule: 27 | - cron: '17 10 * * 2' 28 | 29 | jobs: 30 | analyze: 31 | name: Analyze 32 | runs-on: ubuntu-latest 33 | permissions: 34 | actions: read 35 | contents: read 36 | security-events: write 37 | 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | language: [ 'javascript' ] 42 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 43 | # Learn more: 44 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 45 | 46 | steps: 47 | - name: Checkout repository 48 | uses: actions/checkout@v3 49 | 50 | # Initializes the CodeQL tools for scanning. 51 | - name: Initialize CodeQL 52 | uses: github/codeql-action/init@v1 53 | with: 54 | config-file: ./.github/codeql/codeql-configuration.yml 55 | languages: ${{ matrix.language }} 56 | # If you wish to specify custom queries, you can do so here or in a config file. 57 | # By default, queries listed here will override any specified in a config file. 58 | # Prefix the list here with "+" to use these queries and those in the config file. 59 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 60 | 61 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 62 | # If this step fails, then you should remove it and run the build manually (see below) 63 | - name: Autobuild 64 | uses: github/codeql-action/autobuild@v1 65 | 66 | # ℹ️ Command-line programs to run using the OS shell. 67 | # 📚 https://git.io/JvXDl 68 | 69 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 70 | # and modify them (or add more) to build your code if your project 71 | # uses a compiled language 72 | 73 | #- run: | 74 | # make bootstrap 75 | # make release 76 | 77 | - name: Perform CodeQL Analysis 78 | uses: github/codeql-action/analyze@v1 79 | -------------------------------------------------------------------------------- /.github/workflows/deploy-documents.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Documents 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | build-test-deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | - name: Set-up Node 14 | uses: actions/setup-node@v3.2.0 15 | with: 16 | node-version: "10.x" 17 | - run: npm install 18 | - run: npm run doc 19 | - name: Deploy 20 | uses: crazy-max/ghaction-github-pages@v2.6.0 21 | with: 22 | target_branch: gh-pages 23 | build_dir: documentation 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/update-package-lock.yaml: -------------------------------------------------------------------------------- 1 | name: Update package-lock.json 2 | 3 | on: 4 | schedule: 5 | # This is probably 6am UTC, which is 10pm PST or 11pm PDT 6 | # Alternatively, 6am local is also fine 7 | - cron: '0 6 * * *' 8 | workflow_dispatch: {} 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-node@v3.1.0 17 | with: 18 | node-version: 12 19 | registry-url: https://registry.npmjs.org/ 20 | 21 | - name: Configure git and update package-lock.json 22 | run: | 23 | git config user.email "santosh.shinde@neosofttech.com" 24 | git config user.name "NeoSoft Bot" 25 | npm install --package-lock-only 26 | git add -f package-lock.json 27 | if git commit -m "Update package-lock.json"; then 28 | git push 29 | fi -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | documentation 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | package-lock.json 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | test-report.xml 22 | 23 | # IDEs and editors 24 | /.idea 25 | .project 26 | .classpath 27 | .c9/ 28 | *.launch 29 | .settings/ 30 | *.sublime-workspace 31 | 32 | # IDE - VSCode 33 | .vscode/* 34 | !.vscode/settings.json 35 | !.vscode/tasks.json 36 | !.vscode/launch.json 37 | !.vscode/extensions.json 38 | 39 | # dotenv 40 | config/env/*.env.dev 41 | config/env/*.env.prod 42 | config/env/*.env.test 43 | 44 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run precommit 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run prepush 5 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "{src,apps,libs,test}/**/*.ts": ["npm run lint"], 3 | "./**/*.{ts,js,json,*rc}": ["npm run format"] 4 | } 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | 4 | # Ignore artifacts: 5 | build 6 | coverage 7 | documentation 8 | logs 9 | node_modules 10 | 11 | # Ignore all HTML files: 12 | *.html 13 | .gitignore 14 | .prettierignore 15 | .husky -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "bracketSpacing": true, 4 | "singleQuote": true, 5 | "trailingComma": "es5", 6 | "endOfLine": "lf", 7 | "overrides": [ 8 | { 9 | "files": "*.json", 10 | "options": { 11 | "singleQuote": false 12 | } 13 | }, 14 | { 15 | "files": ".*rc", 16 | "options": { 17 | "singleQuote": false, 18 | "parser": "json" 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this project 2 | 3 | Please take a moment to review this document in order to make the contribution 4 | process easy and effective for everyone involved. 5 | 6 | Following these guidelines helps to communicate that you respect the time of 7 | the developers managing and developing this open source project. In return, 8 | they should reciprocate that respect in addressing your issue or assessing 9 | patches and features. 10 | 11 | 12 | ## Using the issue tracker 13 | 14 | The issue tracker is the preferred channel for [bug reports](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/contribution/bug-reports.md), 15 | [features requests](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/contribution/feature-requests.md) and [submitting pull 16 | requests](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/contribution/pull-requests.md). 17 | 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | EXPOSE 5000 14 | 15 | CMD [ "npm", "run", "start" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /config/env/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=dev 2 | APP_NAME=rest_api 3 | API_VERSION=v1 4 | APP_ENV=dev 5 | 6 | DB_HOST=127.0.0.1 7 | DB_DATABASE=rest_api 8 | DB_USER=root 9 | DB_PASSWORD=root 10 | DB_PORT=3306 11 | APPLY_ENCRYPTION=0 12 | SECRET_KEY=sTJQgn5E8d8jMY15PhARwDrW4my6bLwE 13 | IV=0123456789abcdef 14 | 15 | EMAIL_HOST=smtp.gmail.com 16 | EMAIL_PORT=465 17 | USER_EMAIL=user-email@example.com 18 | USER_PASSWORD=user-password 19 | 20 | SSL=false 21 | SSL_KEY_PATH='./local/deployment/cert/private-key-without-passphrase.pem' 22 | SSL_CERT_PATH='./local/deployment/cert/public-cert-without-passphrase.pem' 23 | SSL_PASS_PHRASE='428f880cf5bf3e2f31d38ecb15b65eacccc955c6' 24 | 25 | SECRET_JWT_KEY=df3d25e6b2209fe4867e4061b3a6797dfebf8425ed7da69bbc3c15035ee1e85a 26 | JWT_EXPIRES_IN=60s 27 | -------------------------------------------------------------------------------- /config/env/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=dev 2 | APP_NAME=rest_api 3 | API_VERSION=v1 4 | 5 | DB_HOST=127.0.0.1 6 | DB_DATABASE=rest_api 7 | DB_USER=root 8 | DB_PASSWORD=root 9 | DB_PORT=3306 10 | 11 | APPLY_ENCRYPTION=1 12 | SECRET_KEY=sTJQgn5E8d8jMY15PhARwDrW4my6bLwE 13 | IV=0123456789abcdef 14 | 15 | API_VERSIONING=URI 16 | API_VERSIONING_HEADER=custom 17 | API_VERSIONING_KEY='v=' -------------------------------------------------------------------------------- /config/env/test.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=test 2 | 3 | APPLY_ENCRYPTION=1 4 | 5 | SECRET_JWT_KEY=df3d25e6b2209fe4867e4061b3a6797dfebf8425ed7da69bbc3c15035ee1e85a 6 | 7 | APP_NAME=rest_api 8 | API_VERSION=v1 9 | APP_ENV=dev 10 | 11 | DB_HOST=127.0.0.1 12 | DB_DATABASE=rest_api 13 | DB_USER=root 14 | DB_PASSWORD=root 15 | DB_PORT=3306 16 | APPLY_ENCRYPTION=0 17 | SECRET_KEY=sTJQgn5E8d8jMY15PhARwDrW4my6bLwE 18 | IV=0123456789abcdef 19 | 20 | EMAIL_HOST=smtp.gmail.com 21 | EMAIL_PORT=465 22 | USER_EMAIL=user-email@example.com 23 | USER_PASSWORD=user-password 24 | 25 | SSL=false 26 | SSL_KEY_PATH='./local/deployment/cert/private-key-without-passphrase.pem' 27 | SSL_CERT_PATH='./local/deployment/cert/public-cert-without-passphrase.pem' 28 | SSL_PASS_PHRASE='428f880cf5bf3e2f31d38ecb15b65eacccc955c6' 29 | -------------------------------------------------------------------------------- /docker/certs/private-key-without-passphrase.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDKdBCUExDr6Gnv 3 | jNQU0UufBocvbhxrlNZ5cDWN+t6XwIcA/LUZU5TPXLxgK+CYpljdPLrP+Rei48SO 4 | eq0mo9q2m/Rxo/MKGtnDYsJBAGJZAhUHU2aqU8pNbO7wO9PPbn14f4TGq/h5rKmk 5 | uq4gsCz0o7RFc4MLQ+E2z9WjVZgfUxM5oJs66rxZyVjJ0I6pubVHG2zD8zygXqaJ 6 | kBCkeE9K5wcJ34gVUVb+g2pOAdAKpuVulf72KV4vm+4aJKJAojq5jj5723wlXFG1 7 | LnQ8moe9ejewZSF4S0Raq5v8ZYwBSWWY7aapNZoM3jeJ/CQTGjJAhVxrlDPrActH 8 | ZfstY7NYqbXxIcVKGlMQkqKEXu3b5nsLC/tGIsVnOVL00PbXT0d99VroPloT+w5a 9 | xtXX+lkwqWN25o76e7k3rWtUF4yKIv5hTUAeLecBYmsh3FR6hpjtU19v5BGJjZfq 10 | Q6fqnICThLDzvi+md6MoFAAr+bf211DbWTyF5F7ucPbuHixf4W23u/V6JMt5V/OH 11 | ixhlnMg0+y8xMnEHGVv1A8u1Ls2pmVIUW1xrMhUBI7pSrZbPUz//u7ODN4RZfwWg 12 | DfWd3bUxGX8KvxU4PD29hixfupdq1SDCSVxPSlDpIGkC4WJwsBCGzvH8okYPg6Fu 13 | EajMUgarB9QcQUlbJyLX1VOrtf97wQIDAQABAoICAQCaBltmjGrfT97oxTfE7sad 14 | ohXPW/J7Vq0ljtzvu+EFixArDBerwQ8CBAe9EyS4fjY1ezKVb2kwICE2VNbPAvIu 15 | 48/BdX4+PddW8xfe5C/uj8r329fL1hufdDUEyD+JHQKQXlPh4owT5ezayrwXWnGZ 16 | XcKU5CDavTXmwZBDx6cIjeeefDYdSOErXigSwn0HW6YzBwZkvfVt/RSuq9A9kNqi 17 | ncmZFVsu/Mc2szx5RJ/Gutrqm2tyVF630eYANcbLbaFH8pRPHtkAg5AWS9VbFiYX 18 | XZhot0gTgQ/r+4udJpSxlemZw5R8P6+IArg2XoIDlnaM5+k8X3BwZtkE5O2HxsZ2 19 | XxXnGCyNY9GAfqkW5RSHGFAejAviGTktTO94ChESSzJzrpFd6sOkhJzWR1n1F3Vh 20 | BkBBuTQqhg1c3h9cRyAr4cNEb6u2paXwODeakCUziwKhtLKfy7+G1X1qJYMc/ng2 21 | 6Nn8v+TdAc1PpgJn7kl+ATR4XpD1OwulrdojRmDPNkGTnbWb5e8AtZ6rw4yiqVGH 22 | WnX8/Y1NAEm6sjLvzv9DadzOU1dGyu7KjfneZqZutKNYPBO/9j64pK0LBsl1HNJu 23 | prgX57CxMrHIG1VtDOqMgZyw7njFdHFpdpWGSqS07IaNuEcu6p5I9Xg5YGrfnS+M 24 | IxUpb4PlX60fQ7DC+Nm3AQKCAQEA8LQ0bUkVtCW9f5fjVwCclKAa4sKjszBTWlaq 25 | 4Y2vnqcMJQIw9cMs5n5JEO6YsxYwEZy2HgdcT56As8nGi7OkFE+DU3JYhJ6oVbS/ 26 | AlE8OzWzAyy3uexGDzEh7Tya2luEedMPqPEGIz+NFmAGX4mRE+uErIS4Tebke1qo 27 | fpBKq0qKy45ZhczFmguQy5ERFKnPx19r1ZRSqYXIqqrA/UEqEMIXfpdswKo68EGE 28 | i5sxVI6IUjBt2xKpEfV7QDObiAgq0Xp/vxwSjEw6Ehy9OYIPGVJnUtYSdTQxWFG5 29 | xzMkrJBCUtiNJmST4itcmDUDDe34dxr+9jrCHKu5I19Dfkog2QKCAQEA11GYpqpN 30 | qIoXP297EQcfqsliaQoQomqX1Y4J2a0nIlGLlUu715rJtdt9FGUpDK0Td/+PV5BT 31 | wOYvc2WSMg4TSqLsa0sUf2yWQsnh6x8ihtUgB1mS5b0Vi8+rYpyrvNxESmz88bod 32 | GhI6d8rqorJuf5a+ZN74GbL1EKjGBXRqsYCMypEEA8HwSH8SXBV//+R54jrQs6mS 33 | 6VfPqasesj3ZAnOQGFHujmMq7v9n9LwGreigLpxNpOg8wwuA+hiIva4doGWmv0br 34 | DkxGXn0jh4ODox0lCSnmwOi6SYYE7/w+L4P+arMPZnQ5v2YYk70v5ECTFp73r3/G 35 | g1tWjY/nRl5hKQKCAQEAjNRieC5yjFwoayaq1JR/CFZEZLgMnyJ/IjPi9uS3A9Qo 36 | vt5xVtlCw/jPNyDiJqWsqiFOTHNyBwNtucMsb0BDl76Nz5zvPWi2hK6jsu9klh/t 37 | BMZ9GEKtGuZcOzliQK+a6swaNXYPaUAdhCHamLscLtN1ZJqiWrHkrGd2fdZeWanb 38 | Ww2GQ0pzspVhuji+DaDs+HFT3jpRwu2LYo9VKtedjQCCToa6El+G7Fro+eVrD1X1 39 | XGEJZUudr+w4mG+ZaBWfN8C8TUxXbc6Li11VOPhnbSFRYp3tXrWSyWHHmEzMAErH 40 | A0HAE950x7cnDca2asf4v3cEBXyLZcrzH0b1bkZgwQKCAQAzaSgpo0AnndEvNE8O 41 | IcVN4ge9TMnr/ceDFgcEEnPlndLWMt8G+85tvZp/5CeI/0pGvJ5dKfjVdiCeTQI7 42 | MWEp0PziIWsjganfCJUIrsZvqzOajailciBS4aLqSL44ud8UZjF/BhEsyG1tbOXZ 43 | MwFvNW8AZryWZkRdVe7yHnnbJ/Z152ot+4CDztNzaS7GyqbPfXuXakXJsU3EFTef 44 | F2VZUH53UBjFpqf5qMIJjVuK6qBUgL2JQmo/p9+D15fOsmQgiCToRuaY411p8jcp 45 | hB1KNQtCxN5z8R2JsJY9Nt0iO0Hj+B0VOWsQaDA2FlX5CfIU1KAPJ1MeyhBAfLub 46 | 8a0BAoIBAADPnGfSyaUrPTaCsvHCZ5JIDigvq6DJksyDs+mMjVyh2GvZVRJgibj1 47 | cSiU4A7p9KTJwW+dvbYc6CfPGpA2u689a6QgoLW3/b3/erXN4H4UdV1uOO8jI8CN 48 | +qEPz+UhVthvdnTrEMeBwQoC3rwAdVN0S4a3BZ2REn36gQONlRAuEYHCaEYymKIK 49 | /9Bz+rX5MVxILLhairDLXhAWW+Rv3ZjpjAZBbXLr7aonba/CRFuPhP+Q5cmL+iBb 50 | 39MFQB0Zd2NuByAqY9fRovQ03o5I75EdaFNPLbxdvkcmLfr944BOlVYNPf6+uNTX 51 | fy1kAy7O8Kl9HKKHYrAGVPdJdlJo5ZE= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /docker/certs/public-cert-without-passphrase.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFpzCCA4+gAwIBAgIUB0SoEW2RVKGnDxwkXapeuasJXk4wDQYJKoZIhvcNAQEL 3 | BQAwYzELMAkGA1UEBhMCSU4xEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEcMBoGA1UEAwwTdGVzdHVybC5uZW9z 5 | b2Z0LmNvbTAeFw0yMTEwMjcxMjM5MjlaFw0yMjEwMjcxMjM5MjlaMGMxCzAJBgNV 6 | BAYTAklOMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX 7 | aWRnaXRzIFB0eSBMdGQxHDAaBgNVBAMME3Rlc3R1cmwubmVvc29mdC5jb20wggIi 8 | MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKdBCUExDr6GnvjNQU0UufBocv 9 | bhxrlNZ5cDWN+t6XwIcA/LUZU5TPXLxgK+CYpljdPLrP+Rei48SOeq0mo9q2m/Rx 10 | o/MKGtnDYsJBAGJZAhUHU2aqU8pNbO7wO9PPbn14f4TGq/h5rKmkuq4gsCz0o7RF 11 | c4MLQ+E2z9WjVZgfUxM5oJs66rxZyVjJ0I6pubVHG2zD8zygXqaJkBCkeE9K5wcJ 12 | 34gVUVb+g2pOAdAKpuVulf72KV4vm+4aJKJAojq5jj5723wlXFG1LnQ8moe9ejew 13 | ZSF4S0Raq5v8ZYwBSWWY7aapNZoM3jeJ/CQTGjJAhVxrlDPrActHZfstY7NYqbXx 14 | IcVKGlMQkqKEXu3b5nsLC/tGIsVnOVL00PbXT0d99VroPloT+w5axtXX+lkwqWN2 15 | 5o76e7k3rWtUF4yKIv5hTUAeLecBYmsh3FR6hpjtU19v5BGJjZfqQ6fqnICThLDz 16 | vi+md6MoFAAr+bf211DbWTyF5F7ucPbuHixf4W23u/V6JMt5V/OHixhlnMg0+y8x 17 | MnEHGVv1A8u1Ls2pmVIUW1xrMhUBI7pSrZbPUz//u7ODN4RZfwWgDfWd3bUxGX8K 18 | vxU4PD29hixfupdq1SDCSVxPSlDpIGkC4WJwsBCGzvH8okYPg6FuEajMUgarB9Qc 19 | QUlbJyLX1VOrtf97wQIDAQABo1MwUTAdBgNVHQ4EFgQUfPKnI+dZVqVPH735Du3I 20 | 4IIB0YwwHwYDVR0jBBgwFoAUfPKnI+dZVqVPH735Du3I4IIB0YwwDwYDVR0TAQH/ 21 | BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAi0+nIWBM2mnbK0AjYs4GjO2ttxDB 22 | F37YVTPCN5ppONPKtJlruzBEO3Ll1ZRGo/efuanBKxEbinuJnm0jD8rlKdTPaLnt 23 | cA+pPLlOQ/03JVTsxoMraU0cNcN9KtMAWtwSAq8Xbj0/bgypEdtp7jlGUKqQWpWO 24 | 3bF2n7Qa7lyf0RPS4eA4DDH5wJp+9TAPLotsD+Dq093mqVtD2D5HVc3hWqJ/LBhA 25 | +22GFpAWmNinutpsMj9Beb/yAU2P6RBPm1GQvGnSW+p5ZHL4p9PyDCYwWCtt/Icb 26 | EBQspM2AOubM9HWa+8IWNQQOkIMRexYNMTUK3vo+JRQ7a01VDufeShGpIJG4asfR 27 | h83i05/LiJNB2fzBdKEgPlnBlLduAiEZZ8F3YwsFcU6o5aBaP4XJOkP404lrEjZb 28 | lw82ESuXQ1XURVNPymNqeH8siu9qn3sXC56/USVsnGcHfDkqL9fx52J2JBykq4Bn 29 | dulg4va3jSmvavJkcmD30qqnXfqfMR5bHoHEDNVCr+GoJf14RGcjQXQDX201JXxk 30 | elwP+L+byQjPKsMbnKyGFg5MhibnqmK6YyNWwZx+EfrurlTizGbEQuecNTKzqoZY 31 | tqJ6W/vYwRA4VHAY5MTbsdW3BlEj6rskGGl+U0dRF5ewDZH+bWcYjZE+ts4UX9ii 32 | 22zR2G7vP/f8S5c= 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker Compose with MySQL Server 2 | version: '3.3' 3 | services: 4 | database: 5 | image: 'mysql:8' 6 | cap_add: 7 | - SYS_NICE 8 | container_name: '${APP_NAME}_mysql' 9 | hostname: '${APP_NAME}_mysql' 10 | networks: 11 | - internal 12 | ports: 13 | - '127.0.0.1:${DB_PORT}:3306' 14 | volumes: 15 | - ./init.sql:/docker-entrypoint-initdb.d/init.sql 16 | - mysql:/var/lib/mysql 17 | environment: 18 | MYSQL_EXPOSE_PORT: '${DB_PORT}' 19 | MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}' 20 | MYSQL_PASSWORD: '${DB_PASSWORD}' 21 | 22 | nestjs: 23 | build: 24 | context: ./../ 25 | container_name: '${APP_NAME}_nestjs' 26 | volumes: 27 | - ./../:/usr/src/app 28 | networks: 29 | - internal 30 | depends_on: 31 | - database 32 | ports: 33 | - '5000:5000' 34 | environment: 35 | DB_HOST: 'database' 36 | 37 | nginx: 38 | image: nginx:1.20 39 | container_name: '${APP_NAME}_nginx' 40 | networks: 41 | - internal 42 | volumes: 43 | - ./nginx.conf:/etc/nginx/conf.d/default.conf 44 | - ./certs:/etc/nginx/certs 45 | ports: 46 | - '80:80' 47 | - '443:443' 48 | healthcheck: 49 | test: ["CMD", "wget", "-qO-", "http://localhost:5000"] 50 | interval: 2s 51 | timeout: 60s 52 | retries: 30 53 | 54 | volumes: 55 | mysql: 56 | 57 | networks: 58 | internal: -------------------------------------------------------------------------------- /docker/init.sql: -------------------------------------------------------------------------------- 1 | CREATE USER nest; 2 | 3 | CREATE DATABASE IF NOT EXISTS rest_api; 4 | GRANT ALL PRIVILEGES ON rest_api.* TO 'nest'@'%'; 5 | -------------------------------------------------------------------------------- /docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | location / { 5 | root /usr/share/nginx/html; 6 | index index.html index.htm; 7 | } 8 | } 9 | server { 10 | listen 80; 11 | server_name testurl.neosoft.com; 12 | location / { 13 | proxy_pass http://rest_api_nestjs:5000; 14 | } 15 | } 16 | server{ 17 | listen 443 ssl; 18 | server_name testurl.neosoft.com; 19 | 20 | ssl_certificate /etc/nginx/certs/public-cert-without-passphrase.pem; 21 | ssl_certificate_key /etc/nginx/certs/private-key-without-passphrase.pem; 22 | 23 | location / { 24 | proxy_pass http://rest_api_nestjs:5000; 25 | } 26 | } -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src", 4 | "compilerOptions": { 5 | "assets": ["core/mailer/email-templates/*"], 6 | "watchAssets": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-boilerolate", 3 | "version": "0.0.1", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "prebuild": "rimraf dist", 8 | "build": "nest build", 9 | "start": "nest start", 10 | "start:dev": "nest start --watch", 11 | "start:debug": "nest start --debug --watch", 12 | "start:prod": "node dist/main", 13 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 14 | "test": "jest --coverage", 15 | "test:watch": "jest --watch", 16 | "test:custom": "npm test -- test/unit/crypto.spec.ts", 17 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 18 | "test:e2e": "jest .e2e-spec.ts$", 19 | "prettier:check": "prettier --check ./**/*.{ts,js,json,*rc}", 20 | "format": "prettier --write \"./**/*.{ts,js,json,*rc}\" \"test/**/*.ts\"", 21 | "precommit": "lint-staged", 22 | "prepush": "", 23 | "prepare": "husky install", 24 | "db:setup": "docker-compose -f ./docker/docker-compose.yml --env-file ./config/env/.env up -d --build && docker start rest_api_nginx", 25 | "db:setup:dev": "docker-compose -f ./docker/docker-compose.yml --env-file ./config/env/.env.dev up -d --build", 26 | "db:setup:prod": "docker-compose --env-file -f ./docker/docker-compose.yml up ./config/env/.env.prod -d --build", 27 | "db:setup:test": "docker-compose --env-file -f docker/docker-compose.yml ./config/env/.env.test up -d --build", 28 | "doc": "npx @compodoc/compodoc -p tsconfig.json --includesName Documentation --includes wiki/docs --theme stripe", 29 | "document": "npx @compodoc/compodoc -p tsconfig.json --includesName Documentation --includes wiki/docs --theme stripe -s", 30 | "typeorm": "ts-node --require tsconfig-paths/register ./node_modules/typeorm/cli.js --config src/db/typeorm.config.ts", 31 | "migration:generate": "npm run typeorm migration:generate -- -n migration" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/NeoSOFT-Technologies/rest-node-nestjs.git" 36 | }, 37 | "author": "Santosh Shinde", 38 | "license": "", 39 | "keywords": [ 40 | "nestjs-boilerplate", 41 | "nestjs-objection", 42 | "nestjs-starter-template", 43 | "nestjs-template", 44 | "nestjs-starter-kit", 45 | "nestjs-skeleton", 46 | "nestjs-rest-api" 47 | ], 48 | "bugs": { 49 | "url": "https://github.com/NeoSOFT-Technologies/rest-node-nestjs/issues" 50 | }, 51 | "homepage": "https://github.com/NeoSOFT-Technologies/rest-node-nestjs#readme", 52 | "dependencies": { 53 | "@nestjs/common": "^8.4.7", 54 | "@nestjs/config": "^1.2.1", 55 | "@nestjs/core": "^8.4.7", 56 | "@nestjs/jwt": "^8.0.1", 57 | "@nestjs/passport": "^8.2.2", 58 | "@nestjs/platform-express": "^8.4.7", 59 | "@nestjs/typeorm": "~8.0.2", 60 | "@types/bcrypt": "^5.0.0", 61 | "@types/jwt-decode": "^3.1.0", 62 | "bcrypt": "^5.0.1", 63 | "class-transformer": "^0.5.1", 64 | "class-validator": "^0.13.2", 65 | "compression": "^1.7.4", 66 | "dotenv": "^11.0.0", 67 | "express-jwt": "^6.1.2", 68 | "helmet": "5.0.1", 69 | "http-status-codes": "^2.2.0", 70 | "juice": "^8.0.0", 71 | "jwks-rsa": "^2.1.4", 72 | "jwt-decode": "^3.1.2", 73 | "mysql2": "^2.3.3", 74 | "nodemailer": "^6.7.7", 75 | "passport": "^0.6.0", 76 | "passport-jwt": "^4.0.0", 77 | "passport-local": "^1.0.0", 78 | "pug": "^3.0.2", 79 | "reflect-metadata": "^0.1.13", 80 | "rimraf": "^3.0.2", 81 | "rxjs": "^7.5.6", 82 | "typeorm": "^0.2.45", 83 | "winston": "^3.8.1" 84 | }, 85 | "devDependencies": { 86 | "@commitlint/cli": "^16.3.0", 87 | "@commitlint/config-conventional": "^16.2.4", 88 | "@compodoc/compodoc": "^1.1.19", 89 | "@nestjs/cli": "^8.2.8", 90 | "@nestjs/schematics": "^8.0.11", 91 | "@nestjs/swagger": "^5.2.1", 92 | "@nestjs/testing": "^8.4.7", 93 | "@types/compression": "^1.7.2", 94 | "@types/cors": "^2.8.12", 95 | "@types/express": "^4.17.13", 96 | "@types/express-serve-static-core": "^4.17.29", 97 | "@types/jest": "^27.5.2", 98 | "@types/lodash": "^4.14.182", 99 | "@types/node": "^17.0.45", 100 | "@types/nodemailer": "^6.4.4", 101 | "@types/passport-jwt": "^3.0.6", 102 | "@types/pug": "^2.0.6", 103 | "@types/supertest": "^2.0.12", 104 | "@typescript-eslint/eslint-plugin": "^5.31.0", 105 | "@typescript-eslint/parser": "^5.31.0", 106 | "commitizen": "^4.2.5", 107 | "cz-conventional-changelog": "^3.3.0", 108 | "eslint": "^8.20.0", 109 | "eslint-config-prettier": "^8.5.0", 110 | "eslint-plugin-import": "^2.26.0", 111 | "eslint-plugin-prettier": "^4.2.1", 112 | "husky": "^7.0.4", 113 | "jest": "^27.5.1", 114 | "jest-sonar-reporter": "^2.0.0", 115 | "lint-staged": "^12.5.0", 116 | "lodash": "^4.17.21", 117 | "node-mocks-http": "^1.11.0", 118 | "prettier": "^2.7.1", 119 | "supertest": "^6.2.4", 120 | "swagger-ui-express": "^4.5.0", 121 | "ts-jest": "^27.1.5", 122 | "ts-loader": "^9.3.1", 123 | "ts-node": "^10.9.1", 124 | "tsconfig-paths": "^3.11.0", 125 | "typescript": "^4.2.3" 126 | }, 127 | "jest": { 128 | "moduleFileExtensions": [ 129 | "js", 130 | "json", 131 | "ts" 132 | ], 133 | "rootDir": ".", 134 | "testRegex": [ 135 | ".*\\.spec\\.ts$", 136 | ".*\\-spec\\.ts$" 137 | ], 138 | "transform": { 139 | "^.+\\.(t|j)s$": "ts-jest" 140 | }, 141 | "collectCoverageFrom": [ 142 | "**/*.(t|j)s" 143 | ], 144 | "collectCoverage": true, 145 | "testResultsProcessor": "jest-sonar-reporter", 146 | "coverageDirectory": "./coverage", 147 | "testEnvironment": "node", 148 | "roots": [ 149 | "/test/" 150 | ], 151 | "coveragePathIgnorePatterns": [ 152 | "/node_modules/", 153 | "/tests/" 154 | ], 155 | "moduleNameMapper": { 156 | "@app/(.*)": "/src/$1", 157 | "@app": "/src", 158 | "@test/(.*)$": "/test/$1" 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.host.url=https://sonarcloud.io 2 | sonar.projectKey=NeoSOFT-Technologies_rest-node-nestjs 3 | sonar.organization=neosoft-technologies 4 | 5 | # This is the name and version displayed in the SonarCloud UI. 6 | sonar.projectName=rest-node-nestjs 7 | sonar.projectVersion=1.0 8 | 9 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 10 | sonar.sources=. 11 | sonar.exclusions=**/test/**/*.ts 12 | sonar.language=js 13 | sonar.tests=./test 14 | 15 | sonar.javascript.lcov.reportPaths=./coverage/lcov.info 16 | sonar.testExecutionReportPaths=./test-report.xml 17 | 18 | # Encoding of the source code. Default is default system encoding 19 | sonar.sourceEncoding=UTF-8 -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req, Res } from '@nestjs/common'; 2 | import { ApiTags } from '@nestjs/swagger'; 3 | 4 | import { AppService } from '@app/app.service'; 5 | import AppLogger from '@app/core/logger/AppLogger'; 6 | 7 | @ApiTags('nest_app') 8 | @Controller() 9 | export class AppController { 10 | constructor(private readonly appService: AppService, private readonly appLogger: AppLogger) {} 11 | 12 | @Get() 13 | getHello(): string { 14 | this.appLogger.log('API called'); 15 | return this.appService.getHello(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { AppController } from '@app/app.controller'; 4 | import { AppService } from '@app/app.service'; 5 | import { AuthModule } from '@app/core/auth/auth.module'; 6 | import { DatabaseModule } from '@app/core/db/database.module'; 7 | import { CoreModule } from '@app/core/module'; 8 | import { UsersModule } from '@app/feature/users/users.module'; 9 | 10 | @Module({ 11 | imports: [CoreModule, DatabaseModule, UsersModule, AuthModule], 12 | controllers: [AppController], 13 | 14 | providers: [AppService], 15 | }) 16 | export class AppModule {} 17 | -------------------------------------------------------------------------------- /src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | public getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/config/app.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | export default registerAs('app', () => ({ 4 | name: process.env.APP_NAME || 'rest_api', 5 | version: process.env.API_VERSION || 'v1', 6 | env: process.env.APP_ENV || 'local', 7 | debug: +process.env.APP_DEBUG || 1, 8 | url: process.env.APP_URL || 'localhost', 9 | port: +process.env.APP_PORT || 5000, 10 | logFileName: './logs/app.log', 11 | applyEncription: +process.env.APPLY_ENCRYPTION, 12 | })); 13 | -------------------------------------------------------------------------------- /src/config/authentication.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | export default registerAs('auth', () => ({ 4 | secretKey: process.env.SECRET_JWT_KEY || 'secretKey123456', 5 | jwtTokenForTest: 6 | process.env.JWT_TOKEN || 'eyJhbGciOiJIUzI1NiJ9.U2FudG9zaDE.OJXhSkvY3NKSxPSLtzmkrAnqIXeMzUTx8Sy5ADMggBo', 7 | })); 8 | -------------------------------------------------------------------------------- /src/config/crypto.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | export default registerAs('crypto', () => ({ 4 | secretKey: process.env.SECRET_KEY || 'sTJQgn5E8d8jMY15PhARwDrW4my6bLwE', 5 | iv: process.env.IV || '0123456789abcdef', 6 | })); 7 | -------------------------------------------------------------------------------- /src/config/database.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | export default registerAs('db', () => ({ 4 | host: process.env.DB_HOST || 'localhost', 5 | port: process.env.DB_PORT || 3306, 6 | username: process.env.DB_USER || 'root', 7 | password: process.env.DB_PASSWORD || 'root', 8 | database: process.env.DB_DATABASE || 'rest_api', 9 | })); 10 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import app from '@app/config/app'; 2 | import auth from '@app/config/authentication'; 3 | import crypto from '@app/config/crypto'; 4 | import db from '@app/config/database'; 5 | import services from '@app/config/services'; 6 | import settings from '@app/config/settings'; 7 | 8 | import mailer from './mailer'; 9 | 10 | export default [app, db, crypto, settings, services, mailer, auth]; 11 | -------------------------------------------------------------------------------- /src/config/mailer.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | export default registerAs('mailer', () => ({ 4 | fromEmail: process.env.USER_EMAIL, 5 | host: process.env.EMAIL_HOST, 6 | port: process.env.EMAIL_PORT, 7 | secure: !!+process.env.SECURE, 8 | username: process.env.USER_EMAIL, 9 | password: process.env.USER_PASSWORD, 10 | })); 11 | -------------------------------------------------------------------------------- /src/config/services.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | // all third-party services' configurations to go here 4 | export default registerAs('services', () => ({})); 5 | -------------------------------------------------------------------------------- /src/config/settings.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | // all your application settings go here. 4 | export default registerAs('settings', () => ({})); 5 | -------------------------------------------------------------------------------- /src/core/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Post, Req, Res } from '@nestjs/common'; 2 | import { ApiBody, ApiTags } from '@nestjs/swagger'; 3 | 4 | import { AuthService } from '@app/core/auth/auth.service'; 5 | import { ValidateUserDto } from '@app/feature/users/dto/validate.user.dto'; 6 | 7 | @ApiTags('authentication') 8 | @Controller('auth') 9 | export class AuthController { 10 | constructor(private readonly authService: AuthService) {} 11 | 12 | @Post('login') 13 | @ApiBody({ type: ValidateUserDto }) 14 | async generateToken(@Body() body: ValidateUserDto): Promise { 15 | try { 16 | const user: ValidateUserDto = body; 17 | return await this.authService.generateToken(user); 18 | } catch (e) { 19 | throw e; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/core/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule, ConfigService } from '@nestjs/config'; 3 | import { JwtModule } from '@nestjs/jwt'; 4 | import { PassportModule } from '@nestjs/passport'; 5 | 6 | import { AuthController } from '@app/core/auth/auth.controller'; 7 | import { AuthService } from '@app/core/auth/auth.service'; 8 | import { JwtStrategy } from '@app/core/auth/jwt.strategy'; 9 | import { UsersService } from '@app/feature/users/services/users.service'; 10 | import { UsersModule } from '@app/feature/users/users.module'; 11 | 12 | @Module({ 13 | imports: [ 14 | UsersModule, 15 | JwtModule.registerAsync({ 16 | imports: [ConfigModule], 17 | useFactory: async (config: ConfigService) => { 18 | return { 19 | secret: config.get('SECRET_JWT_KEY'), 20 | signOptions: { 21 | expiresIn: config.get('JWT_EXPIRES_IN'), 22 | }, 23 | }; 24 | }, 25 | inject: [ConfigService], 26 | }), 27 | ConfigModule, 28 | PassportModule, 29 | ], 30 | providers: [AuthService, JwtStrategy, UsersService], 31 | controllers: [AuthController], 32 | // exports:[AuthService] 33 | }) 34 | export class AuthModule {} 35 | -------------------------------------------------------------------------------- /src/core/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; 2 | import { JwtService } from '@nestjs/jwt'; 3 | 4 | import { comparePassword } from '@app/core/hashing/hashing'; 5 | import { ValidateUserDto } from '@app/feature/users/dto/validate.user.dto'; 6 | import { User } from '@app/feature/users/entities/user.entity'; 7 | import { UsersService } from '@app/feature/users/services/users.service'; 8 | 9 | @Injectable() 10 | export class AuthService { 11 | constructor(private readonly usersService: UsersService, private readonly jwtService: JwtService) {} 12 | 13 | findUserByEmail(user: ValidateUserDto): Promise { 14 | return this.usersService.findEmail(user.email); 15 | } 16 | 17 | async generateToken(user: ValidateUserDto): Promise { 18 | try { 19 | const userData = await this.findUserByEmail(user); 20 | const isMatched = await comparePassword(user.password, userData.password); 21 | if (isMatched) { 22 | const payload = { 23 | id: userData.id, 24 | firstName: userData.firstName, 25 | email: userData.email, 26 | }; 27 | const accessToken = this.jwtService.sign(payload); 28 | return { 29 | access_token: accessToken, 30 | }; 31 | } else { 32 | throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); 33 | } 34 | } catch (e) { 35 | console.error(e); 36 | throw e; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/core/auth/jwt.auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class JwtAuthGuard extends AuthGuard('jwt') {} 6 | -------------------------------------------------------------------------------- /src/core/auth/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { PassportStrategy } from '@nestjs/passport'; 4 | import { ExtractJwt, Strategy } from 'passport-jwt'; 5 | 6 | import { UsersService } from '@app/feature/users/services/users.service'; 7 | 8 | @Injectable() 9 | export class JwtStrategy extends PassportStrategy(Strategy) { 10 | constructor(private readonly config: ConfigService, private readonly usersService: UsersService) { 11 | super({ 12 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 13 | ignoreExpiration: false, 14 | secretOrKey: config.get('auth.secretKey'), 15 | }); 16 | } 17 | 18 | async validate(payload: any) { 19 | await this.usersService.findOne(payload.id); 20 | return 'Success'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/core/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication, ValidationPipe } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import compression from 'compression'; 4 | import cors from 'cors'; 5 | import { json, urlencoded } from 'express'; 6 | import helmet from 'helmet'; 7 | 8 | import { shouldCompress } from '@app/core/compression/compression'; 9 | import { corsOptions } from '@app/core/cors.config'; 10 | import { ErrorHandler, RequestHandler, ResponseHandler } from '@app/core/middleware'; 11 | /** 12 | * Core bootstrap module should be loaded here. 13 | * @param app 14 | * 15 | */ 16 | 17 | export default async function bootstrap(app: INestApplication) { 18 | // Global Prefix 19 | // app.setGlobalPrefix('api'); 20 | 21 | // middlewares, express specific\ 22 | app.use(json({ limit: '50mb' })); 23 | app.use(urlencoded({ limit: '50mb', extended: true })); 24 | app.use(helmet()); 25 | app.use( 26 | compression({ 27 | filter: shouldCompress, 28 | //threshold: 1024, 29 | threshold: 0, 30 | }) 31 | ); 32 | 33 | // CORS configuration 34 | app.use(cors(corsOptions)); 35 | 36 | // Auto-validation 37 | // We'll start by binding ValidationPipe at the application level, thus ensuring all endpoints are protected from receiving incorrect data. 38 | app.useGlobalPipes(new ValidationPipe()); 39 | 40 | // Bind Interceptors 41 | app.useGlobalInterceptors(new RequestHandler(), new ResponseHandler()); 42 | 43 | // Error Handler 44 | app.useGlobalFilters(new ErrorHandler()); 45 | } 46 | -------------------------------------------------------------------------------- /src/core/compression/compression.ts: -------------------------------------------------------------------------------- 1 | import compression from 'compression'; 2 | import { Request, Response } from 'express'; 3 | 4 | export const shouldCompress = (req: Request, res: Response) => { 5 | if (req.headers['x-no-compression']) { 6 | return false; 7 | } 8 | return compression.filter(req, res); 9 | }; 10 | -------------------------------------------------------------------------------- /src/core/cors.config.ts: -------------------------------------------------------------------------------- 1 | export const corsOptions = { 2 | origin: ['https://example1.com', 'https://example2.com', 'https://127.0.0.1:5500'], 3 | }; 4 | -------------------------------------------------------------------------------- /src/core/crypto/crypto.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | 3 | import { ConfigService } from '@nestjs/config'; 4 | 5 | // algorithm - AES 256 GCM Mode 6 | const algorithm = 'aes-256-gcm'; 7 | 8 | // iterations: It must be a number and should be set as high as possible. 9 | // So, the more is the number of iterations, the more secure the derived key will be, 10 | // but in that case it takes greater amount of time to complete. 11 | // number of interation - the value of 2145 is randomly chosen 12 | const iterations = 2145; 13 | 14 | // keylen: It is the key of the required byte length and it is of type number. 15 | // derive encryption key: 32 byte key length 16 | const keylen = 32; 17 | 18 | // digest: It is a digest algorithms of string type. 19 | const digest = 'sha512'; 20 | 21 | // salt 22 | const salt = crypto.randomBytes(64); 23 | 24 | export const encrypt = (config: ConfigService, data: any) => { 25 | try { 26 | // constant to encrypt the data 27 | const inputEncoding = 'utf8'; 28 | const outputEncoding = 'base64'; 29 | 30 | // password - master key 31 | const password = config.get('crypto.secretKey'); 32 | 33 | // random initialization vector 34 | const iv = crypto.randomBytes(12); 35 | 36 | // The method gives an asynchronous Password-Based Key Derivation 37 | const key: Buffer = crypto.pbkdf2Sync(password, salt, iterations, keylen, digest); 38 | 39 | // create a Cipher object, with the stated algorithm, key and initialization vector (iv). 40 | // @algorithm - AES 256 GCM Mode 41 | // @key 42 | // @iv 43 | // @options 44 | const cipher = crypto.createCipheriv(algorithm, key, iv); 45 | 46 | // create a Cipher object, with the stated algorithm, key and initialization vector (iv). 47 | // @algorithm - AES 256 GCM Mode 48 | // @key 49 | // @iv 50 | // @options 51 | const enc1 = cipher.update(JSON.stringify(data), inputEncoding); 52 | 53 | // Return the buffer containing the value of cipher object. 54 | // @outputEncoding: Output encoding format 55 | // const enc2 = cipher.final(); 56 | const enc2 = cipher.final(); 57 | 58 | // extract the auth tag 59 | const tag = cipher.getAuthTag(); 60 | 61 | // return the result 62 | return Buffer.concat([enc1, enc2, iv, tag]).toString(outputEncoding); 63 | } catch (exception) { 64 | throw new Error(exception); 65 | } 66 | }; 67 | 68 | export const decrypt = (config: ConfigService, data: any) => { 69 | try { 70 | // constant to decrypt the data 71 | const inputEncoding = 'base64'; 72 | const outputEncoding = 'utf8'; 73 | 74 | // Creates a new Buffer containing the given JavaScript string {str} 75 | const bufferData = Buffer.from(data, inputEncoding); 76 | 77 | // password - master key 78 | const password = config.get('crypto.secretKey'); 79 | 80 | // derive key using; 32 byte key length 81 | const key = crypto.pbkdf2Sync(password, salt, iterations, keylen, digest); 82 | 83 | // extract iv from encrypted data 84 | const iv = bufferData.slice(bufferData.length - 28, bufferData.length - 16); 85 | 86 | // extract tag from encrypted data 87 | const tag = bufferData.slice(bufferData.length - 16); 88 | 89 | // extract encrypted text from encrypted data 90 | const text = bufferData.slice(0, bufferData.length - 28); 91 | 92 | // AES 256 GCM Mode 93 | const decipher = crypto.createDecipheriv(algorithm, key, iv); 94 | 95 | // set the auth tag 96 | decipher.setAuthTag(tag); 97 | 98 | // Used to update the cipher with data according to the given encoding format. 99 | // @data: It is used to update the cipher by new content 100 | // @inputEncoding: Input encoding format 101 | // @outputEncoding: Output encoding format 102 | let str = decipher.update(text, null, outputEncoding); 103 | 104 | // Return the buffer containing the value of cipher object. 105 | // @outputEncoding: Output encoding format 106 | str += decipher.final(outputEncoding); 107 | 108 | // parse the string decrypted data 109 | return JSON.parse(str); 110 | } catch (exception) { 111 | throw new Error(exception); 112 | } 113 | }; 114 | -------------------------------------------------------------------------------- /src/core/db/database.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule, ConfigService } from '@nestjs/config'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | 5 | import { User } from '@app/feature/users/entities/user.entity'; 6 | 7 | @Module({ 8 | imports: [ 9 | TypeOrmModule.forRootAsync({ 10 | imports: [ConfigModule], 11 | inject: [ConfigService], 12 | useFactory: (config: ConfigService) => ({ 13 | type: 'mysql', 14 | host: config.get('db.host'), 15 | port: config.get('db.port'), 16 | username: config.get('db.username'), 17 | password: config.get('db.password'), 18 | database: config.get('db.database'), 19 | entities: [User], 20 | synchronize: config.get('app.env') === 'local' || 'dev' ? true : false, 21 | migrations: ['dist/db/migrations/*.js'], 22 | migrationsRun: true, 23 | }), 24 | }), 25 | ], 26 | }) 27 | export class DatabaseModule {} 28 | -------------------------------------------------------------------------------- /src/core/db/typeorm.config.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | import { ConnectionOptions } from 'typeorm'; 3 | 4 | import { User } from '@app/feature/users/entities/user.entity'; 5 | 6 | config({ path: 'config/env/.env' }); 7 | 8 | const configa: ConnectionOptions = { 9 | type: 'mysql', 10 | host: process.env.DB_HOST, 11 | port: +process.env.DB_PORT, 12 | username: process.env.DB_USER, 13 | password: process.env.DB_PASSWORD, 14 | database: process.env.DB_DATABASE, 15 | entities: [User], 16 | synchronize: false, 17 | cli: { 18 | migrationsDir: 'src/db/migrations', 19 | }, 20 | }; 21 | 22 | export = configa; 23 | -------------------------------------------------------------------------------- /src/core/hashing/hashing.ts: -------------------------------------------------------------------------------- 1 | import * as bcrypt from 'bcrypt'; 2 | 3 | export async function hashPassword(password: string) { 4 | const salt = await bcrypt.genSalt(); 5 | return bcrypt.hash(password, salt); 6 | } 7 | 8 | export async function comparePassword(password: string, hashedPassword: string) { 9 | return await bcrypt.compare(password, hashedPassword); 10 | } 11 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './middleware'; 2 | -------------------------------------------------------------------------------- /src/core/logger/AppLogger.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, LoggerService } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Logger, createLogger, format, transports } from 'winston'; 4 | 5 | enum WinstonLogLevel { 6 | INFO = 'info', 7 | ERROR = 'error', 8 | WARN = 'WARN', 9 | HTTP = 'HTTP', 10 | VERBOSE = 'verbose', 11 | DEBUG = 'debug', 12 | SILLY = 'silly', 13 | } 14 | 15 | @Injectable() 16 | export default class AppLogger implements LoggerService { 17 | public logger: Logger; 18 | constructor(config: ConfigService) { 19 | const { combine, timestamp, label, printf } = format; 20 | const customLoggerFormat = printf( 21 | ({ level, message, Label, Timestamp }: { level: string; message: string; Label: string; Timestamp: string }) => { 22 | return `${Timestamp} [${Label}] ${level}: ${message}`; 23 | } 24 | ); 25 | this.logger = createLogger({ 26 | format: combine(label({ label: config.get('app.name') }), timestamp(), customLoggerFormat), 27 | transports: [new transports.Console(), new transports.File({ filename: config.get('app.logFileName') })], 28 | }); 29 | } 30 | 31 | log(message: any) { 32 | this.logger.log(WinstonLogLevel.INFO, message); 33 | } 34 | error(message: any) { 35 | this.logger.log(WinstonLogLevel.ERROR, message); 36 | } 37 | warn(message: any) { 38 | this.logger.log(WinstonLogLevel.WARN, message); 39 | } 40 | debug?(message: any) { 41 | this.logger.log(WinstonLogLevel.DEBUG, message); 42 | } 43 | verbose?(message: any) { 44 | this.logger.log(WinstonLogLevel.VERBOSE, message); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/core/logger/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AppLogger'; 2 | -------------------------------------------------------------------------------- /src/core/middleware/ErrorHandler.ts: -------------------------------------------------------------------------------- 1 | import { ExceptionFilter, Catch, ArgumentsHost, HttpException, Logger, HttpStatus } from '@nestjs/common'; 2 | import { Response } from 'express'; 3 | 4 | @Catch() 5 | export class ErrorHandler implements ExceptionFilter { 6 | private readonly logger: Logger; 7 | constructor() { 8 | this.logger = new Logger('EXCEPTION'); 9 | } 10 | 11 | catch(exception: any, host: ArgumentsHost) { 12 | const ctx = host.switchToHttp(); 13 | const response = ctx.getResponse(); 14 | 15 | let statusCode: number, message: any; 16 | if (exception instanceof HttpException) { 17 | statusCode = exception.getStatus(); 18 | message = exception.message; 19 | } else { 20 | statusCode = HttpStatus.INTERNAL_SERVER_ERROR; 21 | message = 'Internal server error'; 22 | } 23 | 24 | this.logger.error({ statusCode, message }); 25 | 26 | response.status(statusCode).json({ 27 | statusCode, 28 | message, 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/core/middleware/RequestHandler.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { map, Observable } from 'rxjs'; 3 | 4 | @Injectable() 5 | export class RequestHandler implements NestInterceptor { 6 | intercept(context: ExecutionContext, next: CallHandler): Observable { 7 | const request = context.switchToHttp().getRequest(); 8 | 9 | const inputs = [request.query, request.body, request.params]; 10 | 11 | for (const input of inputs) { 12 | for (const key in input) { 13 | const value = input[key]; 14 | if (typeof value === 'string' || value instanceof String) { 15 | input[key] = value.trim(); 16 | } 17 | } 18 | } 19 | return next.handle(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/core/middleware/ResponseHandler.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, INestApplication, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { map, Observable } from 'rxjs'; 3 | 4 | @Injectable() 5 | export class ResponseHandler implements NestInterceptor { 6 | intercept(context: ExecutionContext, next: CallHandler): Observable { 7 | return next.handle().pipe( 8 | map((data) => ({ 9 | statusCode: context.switchToHttp().getResponse().statusCode, 10 | response: data, 11 | })) 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/core/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export * from './RequestHandler'; 2 | export * from './ResponseHandler'; 3 | export * from './ErrorHandler'; 4 | -------------------------------------------------------------------------------- /src/core/module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | import { DiscoveryModule } from '@nestjs/core'; 4 | 5 | import config from '@app/config/index'; 6 | 7 | import { exportProvider, getProviders } from './providers'; 8 | 9 | @Global() 10 | @Module({ 11 | imports: [ 12 | DiscoveryModule, 13 | ConfigModule.forRoot({ 14 | envFilePath: [`${process.cwd()}/config/env/.env`, `${process.cwd()}/config/env/.env.${process.env.NODE_ENV}`], 15 | isGlobal: true, 16 | expandVariables: true, 17 | load: config, 18 | }), 19 | ], 20 | providers: [...getProviders()], 21 | exports: [...exportProvider()], 22 | }) 23 | export class CoreModule {} 24 | -------------------------------------------------------------------------------- /src/core/providers.ts: -------------------------------------------------------------------------------- 1 | import AppLogger from './logger/AppLogger'; 2 | 3 | export const getProviders = (): [any] => { 4 | return [AppLogger]; 5 | }; 6 | 7 | export const exportProvider = (): [any] => { 8 | return [AppLogger]; 9 | }; 10 | -------------------------------------------------------------------------------- /src/feature/users/constants/api.response.dto.ts: -------------------------------------------------------------------------------- 1 | export const apiResponse = { 2 | apiUserCreatedResponse: 'User has been created successfully', 3 | 4 | apiUserGetResponse: 'Users list returned successfully', 5 | 6 | apiUserGetById: 'User with specified Id returned successfully', 7 | 8 | apiUserUpdatedResponse: 'User with specified id updated successfully', 9 | 10 | apiUserDeletedResponse: 'User with specified id deleted successfully', 11 | 12 | apiCreateUserFirstNameProperty: { 13 | type: 'String', 14 | description: 'Firstname of the user', 15 | }, 16 | 17 | apiCreateUserLastNameProperty: { 18 | type: 'String', 19 | description: 'Lastname of the user', 20 | }, 21 | 22 | apiUpdateUserBoolProperty: { 23 | type: 'Boolean', 24 | description: 'Tells us whether user is active or not', 25 | }, 26 | 27 | apiValidateUserEmail: { 28 | type: 'String', 29 | description: 'Email of the user', 30 | }, 31 | 32 | apiValidateUserPass: { 33 | type: 'String', 34 | description: 'Password of the user', 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/feature/users/dto/create.user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | import { apiResponse } from '@app/feature/users/constants/api.response.dto'; 4 | 5 | export class CreateUserDto { 6 | @ApiProperty(apiResponse.apiCreateUserFirstNameProperty) 7 | firstName: string; 8 | 9 | @ApiProperty(apiResponse.apiCreateUserLastNameProperty) 10 | lastName: string; 11 | 12 | @ApiProperty(apiResponse.apiValidateUserEmail) 13 | email: string; 14 | 15 | @ApiProperty(apiResponse.apiValidateUserPass) 16 | password: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/feature/users/dto/update.user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | import { apiResponse } from '@app/feature/users/constants/api.response.dto'; 4 | 5 | export class UpdateUserDto { 6 | @ApiProperty(apiResponse.apiCreateUserFirstNameProperty) 7 | firstName: string; 8 | 9 | @ApiProperty(apiResponse.apiCreateUserLastNameProperty) 10 | lastName: string; 11 | 12 | @ApiProperty(apiResponse.apiUpdateUserBoolProperty) 13 | isActive: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /src/feature/users/dto/validate.user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsEmail, IsNotEmpty, Matches } from 'class-validator'; 3 | 4 | import { apiResponse } from '@app/feature/users/constants/api.response.dto'; 5 | 6 | export class ValidateUserDto { 7 | @IsNotEmpty() 8 | @IsEmail() 9 | @ApiProperty(apiResponse.apiValidateUserEmail) 10 | email: string; 11 | 12 | @IsNotEmpty() 13 | @Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, { 14 | message: 15 | 'password must be minimum eight characters, at least one uppercase letter, one lowercase letter, one number and ' + 16 | 'one special character', 17 | }) 18 | @ApiProperty(apiResponse.apiValidateUserPass) 19 | password: string; 20 | } 21 | -------------------------------------------------------------------------------- /src/feature/users/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail } from 'class-validator'; 2 | import { Column, Entity, ObjectIdColumn, PrimaryGeneratedColumn } from 'typeorm'; 3 | 4 | @Entity() 5 | export class User { 6 | @PrimaryGeneratedColumn() 7 | @ObjectIdColumn() 8 | id: string; 9 | 10 | @Column() 11 | firstName: string; 12 | 13 | @Column() 14 | lastName: string; 15 | 16 | @IsEmail() 17 | @Column() 18 | email: string; 19 | 20 | @Column() 21 | password: string; 22 | 23 | @Column({ default: true }) 24 | isActive: boolean; 25 | } 26 | -------------------------------------------------------------------------------- /src/feature/users/repository/db/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { EntityRepository, Repository, UpdateResult } from 'typeorm'; 3 | 4 | import { CreateUserDto } from '@app/feature/users/dto/create.user.dto'; 5 | import { UpdateUserDto } from '@app/feature/users/dto/update.user.dto'; 6 | import { User } from '@app/feature/users/entities/user.entity'; 7 | import { UserRepository } from '@app/feature/users/repository/user.repository'; 8 | @Injectable() 9 | @EntityRepository(User) 10 | export class UserDbRepository extends Repository implements UserRepository { 11 | constructor() { 12 | super(); 13 | } 14 | 15 | async findUser(id: string): Promise { 16 | try { 17 | return await this.findOneOrFail(id); 18 | } catch (error) { 19 | throw new NotFoundException('User does not exists'); 20 | } 21 | } 22 | 23 | findAllUser(): Promise { 24 | return this.find(); 25 | } 26 | 27 | findUserByEmail(email: string): Promise { 28 | return this.findOneOrFail({ 29 | where: { 30 | email: email, 31 | }, 32 | }); 33 | } 34 | 35 | createUser(payload: CreateUserDto): Promise { 36 | return this.save(payload); 37 | } 38 | // updateUser(id: string, payload: UpdateUserDto): Promise { 39 | // // let id = parseInt(id); 40 | // return this.save({ ...payload, id }); 41 | // } 42 | async updateUser(id: string, user: UpdateUserDto): Promise { 43 | const result = await this.findOne(id); 44 | if (result === undefined) { 45 | throw new Error('User not found in database'); 46 | } 47 | return this.update(id, { 48 | ...user, 49 | }); 50 | } 51 | async deleteUser(id: string): Promise { 52 | const result = await this.findOne(id); 53 | if (result === undefined) { 54 | throw new Error('User not found in database'); 55 | } 56 | await this.delete(id); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/feature/users/repository/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { UpdateResult } from 'typeorm'; 2 | 3 | import { CreateUserDto } from '@app/feature/users/dto/create.user.dto'; 4 | import { UpdateUserDto } from '@app/feature/users/dto/update.user.dto'; 5 | import { User } from '@app/feature/users/entities/user.entity'; 6 | 7 | export interface UserRepository { 8 | findUser(id: string): Promise; 9 | findAllUser(): Promise; 10 | findUserByEmail(email: string): Promise; 11 | createUser(payload: CreateUserDto): Promise; 12 | updateUser(id: string, user: UpdateUserDto): Promise; 13 | deleteUser(id: string): Promise; 14 | } 15 | -------------------------------------------------------------------------------- /src/feature/users/services/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | 4 | import { hashPassword } from '@app/core/hashing/hashing'; 5 | import { CreateUserDto } from '@app/feature/users/dto/create.user.dto'; 6 | import { UpdateUserDto } from '@app/feature/users/dto/update.user.dto'; 7 | import { User } from '@app/feature/users/entities/user.entity'; 8 | import { UserDbRepository } from '@app/feature/users/repository/db/user.repository'; 9 | import { UserRepository } from '@app/feature/users/repository/user.repository'; 10 | 11 | @Injectable() 12 | export class UsersService { 13 | constructor( 14 | @InjectRepository(UserDbRepository) 15 | private readonly usersRepository: UserRepository 16 | ) {} 17 | 18 | findAll(): Promise { 19 | return this.usersRepository.findAllUser(); 20 | } 21 | 22 | findOne(id: string): Promise { 23 | return this.usersRepository.findUser(id); 24 | } 25 | 26 | findEmail(email: string): Promise { 27 | return this.usersRepository.findUserByEmail(email); 28 | } 29 | 30 | async save(user: CreateUserDto): Promise { 31 | const password = await hashPassword(user.password); 32 | await this.usersRepository.createUser({ ...user, password }); 33 | } 34 | 35 | async update(id: string, user: UpdateUserDto): Promise { 36 | await this.usersRepository.updateUser(id, user); 37 | } 38 | 39 | async remove(id: string): Promise { 40 | await this.usersRepository.deleteUser(id); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/feature/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Delete, Get, Param, Patch, Post, Req, Res, UseGuards } from '@nestjs/common'; 2 | import { ApiBearerAuth, ApiBody, ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; 3 | 4 | import { JwtAuthGuard } from '@app/core/auth/jwt.auth.guard'; 5 | import { apiResponse } from '@app/feature/users/constants/api.response.dto'; 6 | import { CreateUserDto } from '@app/feature/users/dto/create.user.dto'; 7 | import { UpdateUserDto } from '@app/feature/users/dto/update.user.dto'; 8 | import { User } from '@app/feature/users/entities/user.entity'; 9 | import { UsersService } from '@app/feature/users/services/users.service'; 10 | 11 | @ApiTags('user_api') 12 | @Controller('users') 13 | export class UsersController { 14 | constructor(private readonly usersService: UsersService) {} 15 | 16 | @UseGuards(JwtAuthGuard) 17 | @Get() 18 | @ApiOkResponse({ description: apiResponse.apiUserGetResponse }) 19 | @ApiBearerAuth('JWT-auth') 20 | async getUsers(): Promise { 21 | try { 22 | const users: User[] = await this.usersService.findAll(); 23 | return users; 24 | } catch (e) { 25 | throw e; 26 | } 27 | } 28 | 29 | @Post() 30 | @ApiCreatedResponse({ description: apiResponse.apiUserCreatedResponse }) 31 | @ApiBody({ type: CreateUserDto }) 32 | async saveUser(@Body() body: CreateUserDto): Promise { 33 | try { 34 | const user: CreateUserDto = body; 35 | await this.usersService.save(user); 36 | return 'success'; 37 | } catch (e) { 38 | throw e; 39 | } 40 | } 41 | 42 | @UseGuards(JwtAuthGuard) 43 | @Get(':id') 44 | @ApiOkResponse({ description: apiResponse.apiUserGetById }) 45 | @ApiBearerAuth('JWT-auth') 46 | async getUserById(@Param('id') id: string): Promise { 47 | try { 48 | const userById = await this.usersService.findOne(id); 49 | return userById; 50 | } catch (e) { 51 | throw e; 52 | } 53 | } 54 | 55 | @UseGuards(JwtAuthGuard) 56 | @Delete(':id') 57 | @ApiOkResponse({ description: apiResponse.apiUserDeletedResponse }) 58 | @ApiBearerAuth('JWT-auth') 59 | async deleteUser(@Param('id') id: string): Promise { 60 | try { 61 | await this.usersService.remove(id); 62 | return 'Deletion Successfull'; 63 | } catch (e) { 64 | throw e; 65 | } 66 | } 67 | 68 | @UseGuards(JwtAuthGuard) 69 | @Patch(':id') 70 | @ApiOkResponse({ description: apiResponse.apiUserUpdatedResponse }) 71 | @ApiBody({ type: UpdateUserDto }) 72 | @ApiBearerAuth('JWT-auth') 73 | async updateUserById(@Body() body: UpdateUserDto, @Param('id') id: string): Promise { 74 | try { 75 | const updateUser: UpdateUserDto = body; 76 | await this.usersService.update(id, updateUser); 77 | return 'Updation Successfull'; 78 | } catch (e) { 79 | throw e; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/feature/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | 4 | import { UserDbRepository } from '@app/feature/users/repository/db/user.repository'; 5 | import { UsersService } from '@app/feature/users/services/users.service'; 6 | import { UsersController } from '@app/feature/users/users.controller'; 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([UserDbRepository])], 10 | exports: [TypeOrmModule, UsersService], 11 | providers: [UsersService], 12 | controllers: [UsersController], 13 | }) 14 | export class UsersModule {} 15 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { ConfigService } from '@nestjs/config'; 2 | import { NestFactory } from '@nestjs/core'; 3 | 4 | import { AppModule } from '@app/app.module'; 5 | import coreBootstrap from '@app/core/bootstrap'; 6 | import { setupSwagger } from '@app/swagger'; 7 | 8 | async function bootstrap() { 9 | const app = await NestFactory.create(AppModule); 10 | 11 | const config = app.get(ConfigService); 12 | const PORT = config.get('app.port'); 13 | 14 | // If the environment is dev/local/staging, then only swagger will be enabled 15 | const envList = ['dev', 'staging', 'local', 'test']; 16 | 17 | if (envList.includes(config.get('app.env'))) { 18 | setupSwagger(app); 19 | } 20 | 21 | // core bootstrap 22 | // config, environment, pipe, guards, intereceptors 23 | coreBootstrap(app); 24 | 25 | await app.listen(PORT, () => { 26 | console.log(`Listening on ::${PORT}`); 27 | }); 28 | } 29 | bootstrap(); 30 | -------------------------------------------------------------------------------- /src/shared/mailer/email-templates/neosoft_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/src/shared/mailer/email-templates/neosoft_logo.png -------------------------------------------------------------------------------- /src/shared/mailer/email-templates/nest_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/src/shared/mailer/email-templates/nest_logo.png -------------------------------------------------------------------------------- /src/shared/mailer/email-templates/sample.pug: -------------------------------------------------------------------------------- 1 | body 2 | div 3 | h3(style="color:black;font-weight:bold;text-align:center") Welcome to NeoSOFT Technologies!!! 4 | img(src="cid:neosoft_logo", alt="Logo", style="display:block; margin-left:auto; margin-right:auto;") 5 | div 6 | h3(style="text-align:center;font-weight:bold") Thank You for using email service of our NestJS BoilerPlate. 7 | img(src="cid:nest_logo", alt="nestjs" style="display:block; margin-left:auto; margin-right:auto; height:150px; width:150px") 8 | div 9 | a(href="https://github.com/NeoSOFT-Technologies/rest-node-nestjs") 10 | h3(style="text-align:center; font-weight:bold") OFFICIAL GITHUB PAGE -------------------------------------------------------------------------------- /src/shared/mailer/mailer-interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './interfaces'; 2 | -------------------------------------------------------------------------------- /src/shared/mailer/mailer-interfaces/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IMailConfig { 2 | fromEmail: string; 3 | host: string; 4 | port: number; 5 | auth: IMailAuth; 6 | } 7 | 8 | interface IMailAuth { 9 | user: string; 10 | pass: string; 11 | } 12 | 13 | export interface IMailResponse { 14 | success: boolean; 15 | message?: string; 16 | item?: any; 17 | errors?: any; 18 | } 19 | 20 | export interface IMailOptions { 21 | subject: string; 22 | templateName?: string; 23 | body?: string; 24 | htmlBody?: string; 25 | replace?: Record; 26 | to?: string | Array; 27 | } 28 | -------------------------------------------------------------------------------- /src/shared/mailer/mailer.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { EmailHandlerService } from './mailer.service'; 4 | 5 | @Module({ 6 | providers: [EmailHandlerService], 7 | exports: [EmailHandlerService], 8 | }) 9 | export class EmailHandlerModule {} 10 | -------------------------------------------------------------------------------- /src/shared/mailer/mailer.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import juice from 'juice'; 4 | import nodemailer from 'nodemailer'; 5 | import Mail from 'nodemailer/lib/mailer'; 6 | import pug from 'pug'; 7 | 8 | import { IMailConfig, IMailOptions, IMailResponse } from './mailer-interfaces'; 9 | 10 | @Injectable() 11 | export class EmailHandlerService { 12 | constructor(private readonly config: ConfigService) {} 13 | async sendEmail(options: IMailOptions): Promise { 14 | return new Promise( 15 | async (resolve: (value?: IMailResponse) => void, rejects: (reason?: IMailResponse) => void): Promise => { 16 | const mailConfig: IMailConfig = { 17 | fromEmail: this.config.get('mailer.fromEmail'), 18 | host: this.config.get('mailer.host'), 19 | port: this.config.get('mailer.port'), 20 | auth: { 21 | user: this.config.get('mailer.username'), 22 | pass: this.config.get('mailer.password'), 23 | }, 24 | }; 25 | 26 | const server: Mail = await this.getEmailServer(mailConfig); 27 | 28 | const mailOptions: Mail.Options = { 29 | from: mailConfig.fromEmail, 30 | to: options.to, 31 | subject: options.subject, 32 | }; 33 | 34 | // if template name is exist then choose pug template from views 35 | if (options.templateName) { 36 | mailOptions.html = await this.getTemplate(options.templateName, options.replace); 37 | mailOptions.attachments = [ 38 | { 39 | filename: 'neosoft_logo.png', 40 | path: `${__dirname}/email-templates/neosoft_logo.png`, 41 | cid: 'neosoft_logo', 42 | }, 43 | { 44 | filename: 'nest_logo.png', 45 | path: `${__dirname}/email-templates/nest_logo.png`, 46 | cid: 'nest_logo', 47 | }, 48 | ]; 49 | } 50 | 51 | // if text body then assign as text 52 | if (options.body) { 53 | mailOptions.text = options.body; 54 | } 55 | 56 | // if html body then assign as html 57 | if (options.htmlBody) { 58 | mailOptions.html = options.htmlBody; 59 | } 60 | 61 | server.sendMail(mailOptions, (err: Error, info: nodemailer.SentMessageInfo) => { 62 | if (info) { 63 | resolve({ 64 | success: true, 65 | item: info, 66 | }); 67 | } else { 68 | rejects({ 69 | success: false, 70 | errors: err, 71 | }); 72 | } 73 | }); 74 | } 75 | ); 76 | } 77 | 78 | private async getTemplate(templateName: string, options: Record = {}): Promise { 79 | const html: string = pug.renderFile(`${__dirname}/email-templates/${templateName}.pug`, options); 80 | return juice(html); 81 | } 82 | 83 | private async getEmailServer(mailConfig: IMailConfig): Promise { 84 | return nodemailer.createTransport({ 85 | secure: true, 86 | ...mailConfig, 87 | }); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/swagger/index.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 4 | 5 | export const setupSwagger = (app: INestApplication) => { 6 | const config = app.get(ConfigService); 7 | const options = new DocumentBuilder() 8 | .setTitle(config.get('app.name')) 9 | .setDescription(`API Documentation for the app ${config.get('app.name')}`) 10 | .addBearerAuth( 11 | { 12 | type: 'http', 13 | scheme: 'bearer', 14 | bearerFormat: 'JWT', 15 | name: 'JWT', 16 | description: 'Enter JWT token', 17 | in: 'header', 18 | }, 19 | 'JWT-auth' 20 | ) 21 | .build(); 22 | 23 | const document = SwaggerModule.createDocument(app, options); 24 | SwaggerModule.setup('api/docs', app, document); 25 | }; 26 | -------------------------------------------------------------------------------- /test/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import { StatusCodes } from 'http-status-codes'; 4 | import request from 'supertest'; 5 | 6 | import { AppModule } from '@app/app.module'; 7 | import coreBootstrap from '@app/core/bootstrap'; 8 | 9 | describe('AppController (e2e)', () => { 10 | let app: INestApplication; 11 | 12 | beforeAll(async () => { 13 | const moduleFixture: TestingModule = await Test.createTestingModule({ 14 | imports: [AppModule], 15 | }).compile(); 16 | 17 | app = moduleFixture.createNestApplication(); 18 | coreBootstrap(app); 19 | await app.init(); 20 | }); 21 | 22 | afterAll(async () => { 23 | await app.close(); 24 | }); 25 | 26 | it('Should return an hello world string along with status code 200', async () => { 27 | const { body }: any = await request(app.getHttpServer()).get('').expect(StatusCodes.OK); 28 | expect(body.response).toEqual('Hello World!'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/mock/generate-token.stub.ts: -------------------------------------------------------------------------------- 1 | export const loginCredentials = { 2 | email: 'santoshshinde@gmail.com', 3 | password: '123456seven', 4 | }; 5 | -------------------------------------------------------------------------------- /test/mock/user.stub.ts: -------------------------------------------------------------------------------- 1 | export const userStub = () => { 2 | return { 3 | firstName: 'Santosh', 4 | lastName: 'Shinde', 5 | email: 'santoshshinde@gmail.com', 6 | password: '123456seven', 7 | isActive: true, 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /test/mock/user.update.stub.ts: -------------------------------------------------------------------------------- 1 | export const updateUserStub = () => { 2 | return { 3 | firstName: 'Santosh', 4 | lastName: 'Shinde', 5 | isActive: true, 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /test/mock/users.response.ts: -------------------------------------------------------------------------------- 1 | //Change this array according to your data in Database 2 | export const users = [ 3 | { 4 | id: 127, 5 | firstName: 'Santosh', 6 | lastName: 'Shinde', 7 | email: 'santoshshinde@gmail.com', 8 | password: '123456seven', 9 | isActive: true, 10 | }, 11 | ]; 12 | -------------------------------------------------------------------------------- /test/unit/authentication/auth.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { ConfigModule, ConfigService } from '@nestjs/config'; 2 | import { JwtModule } from '@nestjs/jwt'; 3 | import { PassportModule } from '@nestjs/passport'; 4 | import { Test, TestingModule } from '@nestjs/testing'; 5 | 6 | import { AuthController } from '@app/core/auth/auth.controller'; 7 | import { AuthService } from '@app/core/auth/auth.service'; 8 | import { ValidateUserDto } from '@app/feature/users/dto/validate.user.dto'; 9 | 10 | describe('Testing AuthController', () => { 11 | let authController: AuthController; 12 | 13 | const mockAuthService = { 14 | generateToken: jest.fn().mockRejectedValueOnce(new Error('Test Error')).mockResolvedValue('sampleToken'), 15 | }; 16 | const mockBody: ValidateUserDto = { 17 | email: 'email', 18 | password: 'password', 19 | }; 20 | beforeAll(async () => { 21 | const module: TestingModule = await Test.createTestingModule({ 22 | imports: [ 23 | JwtModule.registerAsync({ 24 | imports: [ConfigModule], 25 | useFactory: async (config: ConfigService) => { 26 | return { 27 | secret: config.get('SECRET_JWT_KEY'), 28 | }; 29 | }, 30 | inject: [ConfigService], 31 | }), 32 | ConfigModule, 33 | PassportModule, 34 | ], 35 | controllers: [AuthController], 36 | providers: [AuthService], 37 | }) 38 | .overrideProvider(AuthService) 39 | .useValue(mockAuthService) 40 | .compile(); 41 | 42 | authController = module.get(AuthController); 43 | }); 44 | 45 | it('Testing error cases', async () => { 46 | expect(async () => await authController.generateToken(mockBody)).rejects.toThrowError('Test Error'); 47 | }); 48 | 49 | it('Testing authcontroller "generateToken"', async () => { 50 | expect(await authController.generateToken(mockBody)).toEqual('sampleToken'); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/unit/components/users.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { users } from '@test/mock/users.response'; 3 | 4 | import { CreateUserDto } from '@app/feature/users/dto/create.user.dto'; 5 | import { UpdateUserDto } from '@app/feature/users/dto/update.user.dto'; 6 | import { UsersService } from '@app/feature/users/services/users.service'; 7 | import { UsersController } from '@app/feature/users/users.controller'; 8 | 9 | describe('Testing UsersController', () => { 10 | let usersController: UsersController; 11 | 12 | const mockParam = 'mockID'; 13 | const mockCreateBody: CreateUserDto = { 14 | firstName: 'string', 15 | lastName: 'string', 16 | email: 'string', 17 | password: 'string', 18 | }; 19 | const mockUpdateBody: UpdateUserDto = { 20 | firstName: 'string', 21 | lastName: 'string', 22 | isActive: false, 23 | }; 24 | 25 | const mockUsersService = { 26 | findAll: jest.fn().mockRejectedValueOnce(new Error('Test error')).mockResolvedValue(users), 27 | findOne: jest.fn().mockRejectedValueOnce(new Error('Test error')).mockResolvedValue(users[0]), 28 | remove: jest.fn().mockRejectedValueOnce(new Error('Test error')), 29 | update: jest.fn().mockRejectedValueOnce(new Error('Test error')), 30 | save: jest.fn().mockRejectedValueOnce(new Error('Test error')), 31 | }; 32 | 33 | beforeAll(async () => { 34 | const module: TestingModule = await Test.createTestingModule({ 35 | controllers: [UsersController], 36 | providers: [UsersService], 37 | }) 38 | .overrideProvider(UsersService) 39 | .useValue(mockUsersService) 40 | .compile(); 41 | 42 | usersController = module.get(UsersController); 43 | }); 44 | 45 | it('Testing error cases', async () => { 46 | expect(async () => { 47 | await usersController.getUsers(); 48 | }).rejects.toThrowError('Test error'); 49 | expect(async () => { 50 | await usersController.getUserById('mockID'); 51 | }).rejects.toThrowError('Test error'); 52 | expect(async () => { 53 | await usersController.saveUser(mockCreateBody); 54 | }).rejects.toThrowError('Test error'); 55 | expect(async () => { 56 | await usersController.deleteUser('mockID'); 57 | }).rejects.toThrowError('Test error'); 58 | expect(async () => { 59 | await usersController.updateUserById(mockUpdateBody, 'mockID'); 60 | }).rejects.toThrowError('Test error'); 61 | }); 62 | 63 | it('Testing usercontroller "getUsers"', async () => { 64 | expect(await usersController.getUsers()).toEqual(users); 65 | }); 66 | 67 | it('Testing usercontroller "getUserById"', async () => { 68 | expect(await usersController.getUserById(mockParam)).toEqual(users[0]); 69 | }); 70 | it('Testing usercontroller "saveUser"', async () => { 71 | expect(await usersController.saveUser(mockCreateBody)).toEqual('success'); 72 | }); 73 | 74 | it('Testing usercontroller "deleteUser"', async () => { 75 | expect(await usersController.deleteUser(mockParam)).toEqual('Deletion Successfull'); 76 | }); 77 | 78 | it('Testing usercontroller "updateUserById"', async () => { 79 | expect(await usersController.updateUserById(mockUpdateBody, mockParam)).toEqual('Updation Successfull'); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/unit/components/users.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { getRepositoryToken } from '@nestjs/typeorm'; 3 | import { userStub } from '@test/mock/user.stub'; 4 | 5 | import { CreateUserDto } from '@app/feature/users/dto/create.user.dto'; 6 | import { UpdateUserDto } from '@app/feature/users/dto/update.user.dto'; 7 | import { User } from '@app/feature/users/entities/user.entity'; 8 | import { UserDbRepository } from '@app/feature/users/repository/db/user.repository'; 9 | import { UsersService } from '@app/feature/users/services/users.service'; 10 | 11 | jest.mock('bcrypt', () => ({ 12 | genSalt: jest.fn().mockResolvedValue('Salt'), 13 | hash: jest.fn().mockResolvedValue('123456seven'), 14 | })); 15 | 16 | describe('Testing UsersService', () => { 17 | let usersService: UsersService; 18 | let userId = '1'; 19 | const mockUsersRepository = { 20 | findUser: jest.fn().mockResolvedValue(userStub()), 21 | findAllUser: jest.fn().mockResolvedValue([userStub()]), 22 | createUser: jest.fn().mockResolvedValue(userStub()), 23 | updateUser: jest.fn().mockResolvedValue(userStub()), 24 | deleteUser: jest.fn().mockImplementation((dto) => dto), 25 | }; 26 | 27 | beforeAll(async () => { 28 | const module: TestingModule = await Test.createTestingModule({ 29 | providers: [ 30 | UsersService, 31 | { 32 | provide: getRepositoryToken(UserDbRepository), 33 | useValue: mockUsersRepository, 34 | }, 35 | ], 36 | }).compile(); 37 | 38 | usersService = module.get(UsersService); 39 | }); 40 | 41 | describe('When save method of users service is called', () => { 42 | let createuserDto: CreateUserDto; 43 | beforeAll(async () => { 44 | createuserDto = { 45 | firstName: userStub().firstName, 46 | lastName: userStub().lastName, 47 | email: userStub().email, 48 | password: userStub().password, 49 | }; 50 | await usersService.save(createuserDto); 51 | }); 52 | it('Then save method of users service should be called with a createUserDTO', () => { 53 | expect(mockUsersRepository.createUser).toBeCalled(); 54 | }); 55 | }); 56 | 57 | describe('When findAll method of users service is called', () => { 58 | let users: User[]; 59 | let user: User; 60 | beforeAll(async () => { 61 | users = await usersService.findAll(); 62 | if (users && users.length) { 63 | userId = users[0].id; 64 | } 65 | }); 66 | it('Then usersService should be defined', () => { 67 | expect(usersService).toBeDefined(); 68 | }); 69 | it('Then findAll method of users service should be defined', () => { 70 | expect(usersService.findAll).toBeDefined(); 71 | }); 72 | }); 73 | 74 | describe('When findOne method of users service is called', () => { 75 | let user: User; 76 | beforeAll(async () => { 77 | user = await usersService.findOne(userId); 78 | }); 79 | it('Then findOne method of users service should be called with an id', () => { 80 | expect(mockUsersRepository.findUser).toHaveBeenCalledWith(userId); 81 | }); 82 | it('Then findOne method of users service should return a user', () => { 83 | expect(user).toEqual(userStub()); 84 | }); 85 | }); 86 | 87 | describe('When save method of users service is called', () => { 88 | let createuserDto: CreateUserDto; 89 | beforeAll(async () => { 90 | createuserDto = { 91 | firstName: userStub().firstName, 92 | lastName: userStub().lastName, 93 | email: userStub().email, 94 | password: userStub().password, 95 | }; 96 | await usersService.save(createuserDto); 97 | }); 98 | it('Then save method of users service should be called with a createUserDTO', async () => { 99 | expect(mockUsersRepository.createUser).toHaveBeenCalledWith({ 100 | firstName: userStub().firstName, 101 | lastName: userStub().lastName, 102 | email: userStub().email, 103 | password: userStub().password, 104 | }); 105 | }); 106 | }); 107 | 108 | describe('When update method of users service is called', () => { 109 | let updateuserDto: UpdateUserDto; 110 | beforeAll(async () => { 111 | updateuserDto = { 112 | firstName: userStub().firstName, 113 | lastName: userStub().lastName, 114 | isActive: userStub().isActive, 115 | }; 116 | await usersService.update(userId, updateuserDto); 117 | }); 118 | it('Then update method of users service should be called with a updateUserDTO', () => { 119 | expect(mockUsersRepository.updateUser).toHaveBeenCalledWith(userId, updateuserDto); 120 | }); 121 | }); 122 | 123 | describe('When remove method of users service is called', () => { 124 | beforeAll(async () => { 125 | await usersService.remove(userId); 126 | }); 127 | it('Then remove method of users service should be called with a updateUserDTO', () => { 128 | expect(mockUsersRepository.deleteUser).toHaveBeenCalledWith(userId); 129 | }); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /test/unit/config/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Test, TestingModule } from '@nestjs/testing'; 4 | import { TestCoreModule } from '@test/unit/core/guards/module/core-test.module'; 5 | 6 | describe('Testing config variables default values', () => { 7 | let app: INestApplication; 8 | 9 | beforeAll(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [TestCoreModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | afterAll(async () => { 19 | await app.close(); 20 | }); 21 | 22 | describe('Testing "app.ts"', () => { 23 | it('"app.name" should be defined', async () => { 24 | const config = app.get(ConfigService); 25 | expect(config.get('app.name')).toBeDefined(); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/config/crypto.spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Test, TestingModule } from '@nestjs/testing'; 4 | import { TestCoreModule } from '@test/unit/core/guards/module/core-test.module'; 5 | 6 | describe('Testing config variables default values', () => { 7 | let app: INestApplication; 8 | 9 | beforeAll(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [TestCoreModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | afterAll(async () => { 19 | await app.close(); 20 | }); 21 | 22 | describe('Testing "crypto.ts"', () => { 23 | it('"crypto.secretKey" should be defined', async () => { 24 | const config = app.get(ConfigService); 25 | expect(config.get('crypto.secretKey')).toBeDefined(); 26 | }); 27 | 28 | it('"crypto.iv" should be defined', async () => { 29 | const config = app.get(ConfigService); 30 | expect(config.get('crypto.iv')).toBeDefined(); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/unit/config/db.spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Test, TestingModule } from '@nestjs/testing'; 4 | import { TestCoreModule } from '@test/unit/core/guards/module/core-test.module'; 5 | 6 | describe('Testing config variables default values', () => { 7 | let app: INestApplication; 8 | 9 | beforeAll(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [TestCoreModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | afterAll(async () => { 19 | await app.close(); 20 | }); 21 | 22 | describe('Testing "database.ts"', () => { 23 | it('"db.host" should be defined', async () => { 24 | const config = app.get(ConfigService); 25 | expect(config.get('db.host')).toBeDefined(); 26 | }); 27 | it('"db.port" should be defined', async () => { 28 | const config = app.get(ConfigService); 29 | expect(config.get('db.port')).toBeDefined(); 30 | }); 31 | 32 | it('"db.username" should be defined', async () => { 33 | const config = app.get(ConfigService); 34 | expect(config.get('db.username')).toBeDefined(); 35 | }); 36 | 37 | it('"db.password" should be defined', async () => { 38 | const config = app.get(ConfigService); 39 | expect(config.get('db.password')).toBeDefined(); 40 | }); 41 | 42 | it('"db.database" should be defined', async () => { 43 | const config = app.get(ConfigService); 44 | expect(config.get('db.database')).toBeDefined(); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/unit/core/crypto.spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Test, TestingModule } from '@nestjs/testing'; 4 | 5 | import { AppModule } from '@app/app.module'; 6 | import { decrypt, encrypt } from '@app/core/crypto/crypto'; 7 | 8 | describe('Testing Encryption-Decryption', () => { 9 | let app: INestApplication; 10 | 11 | beforeAll(async () => { 12 | const moduleFixture: TestingModule = await Test.createTestingModule({ 13 | imports: [AppModule], 14 | }).compile(); 15 | 16 | app = moduleFixture.createNestApplication(); 17 | }); 18 | 19 | afterAll(async () => { 20 | await app.close(); 21 | }); 22 | 23 | it('Testing for text', () => { 24 | const config = app.get(ConfigService); 25 | const data = 'This data is to be encrypted'; 26 | const encrypted = encrypt(config, data); 27 | const decrypted = decrypt(config, encrypted); 28 | expect(decrypted).toEqual(data); 29 | }); 30 | 31 | it('Testing for array', () => { 32 | const config = app.get(ConfigService); 33 | const data = ['this', 'is', 'array']; 34 | const encrypted = encrypt(config, data); 35 | const decrypted = decrypt(config, encrypted); 36 | expect(decrypted).toEqual(data); 37 | }); 38 | 39 | it('Testing for object', () => { 40 | const config = app.get(ConfigService); 41 | const data = { 42 | key1: 'value1', 43 | key2: 'value2', 44 | }; 45 | const encrypted = encrypt(config, data); 46 | const decrypted = decrypt(config, encrypted); 47 | expect(decrypted).toEqual(data); 48 | }); 49 | 50 | it('Testing catch block for encryption', () => { 51 | const config = app.get(ConfigService); 52 | const mockget = jest.spyOn(config, 'get'); 53 | mockget.mockImplementation(() => { 54 | throw new Error('Error in encryption'); 55 | }); 56 | const data = 'This data is to be encrypted'; 57 | expect(() => encrypt(config, data)).toThrow(); 58 | mockget.mockRestore(); 59 | }); 60 | 61 | it('Testing catch block for decryption', () => { 62 | const config = app.get(ConfigService); 63 | const data = 'This data is to be encrypted'; 64 | const encrypted = encrypt(config, data); 65 | 66 | const mockget = jest.spyOn(config, 'get'); 67 | mockget.mockImplementation(() => { 68 | throw new Error('Error in decryption'); 69 | }); 70 | expect(() => decrypt(config, encrypted)).toThrow(); 71 | mockget.mockRestore(); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/unit/core/guards/module/core-test.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | 4 | import config from '@app/config/index'; 5 | 6 | @Global() 7 | @Module({ 8 | imports: [ 9 | ConfigModule.forRoot({ 10 | envFilePath: [`${process.cwd()}/config/env/test.env`], 11 | isGlobal: true, 12 | expandVariables: true, 13 | load: config, 14 | }), 15 | ], 16 | }) 17 | export class TestCoreModule {} 18 | -------------------------------------------------------------------------------- /test/unit/core/logger.spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Test, TestingModule } from '@nestjs/testing'; 4 | 5 | import { AppModule } from '@app/app.module'; 6 | import AppLogger from '@app/core/logger/AppLogger'; 7 | 8 | describe('Testing logger', () => { 9 | let app: INestApplication; 10 | 11 | beforeAll(async () => { 12 | const module: TestingModule = await Test.createTestingModule({ 13 | imports: [AppModule], 14 | }).compile(); 15 | 16 | app = module.createNestApplication(); 17 | await app.init(); 18 | }); 19 | 20 | afterAll(async () => { 21 | await app.close(); 22 | }); 23 | 24 | it('Testing log method from AppLogger class', () => { 25 | const config = app.get(ConfigService); 26 | const appLogger = new AppLogger(config); 27 | const logMock = jest.spyOn(appLogger, 'log'); 28 | logMock.mockImplementation(() => 'This is myLogger'); 29 | 30 | expect(appLogger.log('Test string')).toEqual('This is myLogger'); 31 | expect(logMock).toHaveBeenCalled(); 32 | }); 33 | 34 | it('Testing error method from AppLogger class', () => { 35 | const config = app.get(ConfigService); 36 | const appLogger = new AppLogger(config); 37 | const errorMock = jest.spyOn(appLogger, 'error'); 38 | errorMock.mockImplementation(() => 'This is myError'); 39 | 40 | expect(appLogger.error('Test string')).toEqual('This is myError'); 41 | expect(errorMock).toHaveBeenCalled(); 42 | }); 43 | 44 | it('Testing warn method from AppLogger class', () => { 45 | const config = app.get(ConfigService); 46 | const appLogger = new AppLogger(config); 47 | const warnMock = jest.spyOn(appLogger, 'warn'); 48 | warnMock.mockImplementation(() => 'This is myWarn'); 49 | 50 | expect(appLogger.warn('Test string')).toEqual('This is myWarn'); 51 | expect(warnMock).toHaveBeenCalled(); 52 | }); 53 | 54 | it('Testing debug method from AppLogger class', () => { 55 | const config = app.get(ConfigService); 56 | const appLogger = new AppLogger(config); 57 | const debugMock = jest.spyOn(appLogger, 'debug'); 58 | debugMock.mockImplementation(() => 'This is myDebug'); 59 | 60 | expect(appLogger.debug('Test string')).toEqual('This is myDebug'); 61 | expect(debugMock).toHaveBeenCalled(); 62 | }); 63 | 64 | it('Testing verbose method from AppLogger class', () => { 65 | const config = app.get(ConfigService); 66 | const appLogger = new AppLogger(config); 67 | const verboseMock = jest.spyOn(appLogger, 'verbose'); 68 | verboseMock.mockImplementation(() => 'This is myVerbose'); 69 | 70 | expect(appLogger.verbose('Test string')).toEqual('This is myVerbose'); 71 | expect(verboseMock).toHaveBeenCalled(); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/unit/core/mailer.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { CoreModule } from '@app/core/module'; 4 | import { EmailHandlerService } from '@app/shared/mailer/mailer.service'; 5 | 6 | jest.mock('nodemailer', () => ({ 7 | createTransport: jest.fn().mockReturnValue({ 8 | sendMail: jest.fn().mockImplementation((mailoptions, callback) => { 9 | callback(null, mailoptions); 10 | }), 11 | verify: jest.fn().mockImplementation((callback) => { 12 | callback(); 13 | }), 14 | }), 15 | })); 16 | 17 | describe('Testing mailer', () => { 18 | let mailerService: EmailHandlerService; 19 | 20 | beforeAll(async () => { 21 | const module: TestingModule = await Test.createTestingModule({ 22 | imports: [CoreModule], 23 | providers: [EmailHandlerService], 24 | }).compile(); 25 | 26 | mailerService = module.get(EmailHandlerService); 27 | }); 28 | 29 | it('Sending email with pug template', async () => { 30 | const response = await mailerService.sendEmail({ 31 | to: 'recipient-email@example.com', 32 | subject: 'Email subject', 33 | templateName: 'sample', 34 | replace: { name: 'NestJs' }, 35 | }); 36 | expect(response.success).toBe(true); 37 | expect(response.item.html).toBeDefined(); 38 | }); 39 | it('Sending email with text body', async () => { 40 | const response = await mailerService.sendEmail({ 41 | to: 'recipient-email@example.com', 42 | subject: 'Email subject', 43 | body: 'Email body', 44 | }); 45 | expect(response.success).toBe(true); 46 | expect(response.item.text).toBeDefined(); 47 | }); 48 | 49 | it('Sending email with html body', async () => { 50 | const response = await mailerService.sendEmail({ 51 | to: 'recipient-email@example.com', 52 | subject: 'Email subject', 53 | htmlBody: '

Email body

', 54 | }); 55 | expect(response.success).toBe(true); 56 | expect(response.item.html).toBeDefined(); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/unit/core/typeorm.spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Test, TestingModule } from '@nestjs/testing'; 4 | import { Connection, createConnection } from 'typeorm'; 5 | 6 | import { CoreModule } from '@app/core/module'; 7 | import { User } from '@app/feature/users/entities/user.entity'; 8 | 9 | describe('Testing Typeorm connection', () => { 10 | let app: INestApplication; 11 | 12 | beforeAll(async () => { 13 | const moduleFixture: TestingModule = await Test.createTestingModule({ 14 | imports: [CoreModule], 15 | }).compile(); 16 | 17 | app = moduleFixture.createNestApplication(); 18 | await app.init(); 19 | }); 20 | 21 | it('Testing mysql connection', async () => { 22 | const config = app.get(ConfigService); 23 | const connection: Connection = await createConnection({ 24 | type: 'mysql', 25 | host: config.get('db.host'), 26 | port: config.get('db.port'), 27 | username: config.get('db.username'), 28 | password: config.get('db.password'), 29 | database: config.get('db.database'), 30 | entities: [User], 31 | synchronize: true, 32 | }); 33 | expect(connection).toBeDefined(); 34 | await connection.close(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tsconfig-paths-bootstrap.js: -------------------------------------------------------------------------------- 1 | import { compilerOptions } from './tsconfig.json'; 2 | import { register } from 'tsconfig-paths'; 3 | 4 | const baseUrl = './dist'; 5 | register({ 6 | baseUrl, 7 | paths: compilerOptions.paths, 8 | }); 9 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "target": "es2017", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true, 15 | "paths": { 16 | "@app": ["src"], 17 | "@app/*": ["src/*"], 18 | "@test/*": ["test/*"] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /wiki/docs/contribution.md: -------------------------------------------------------------------------------- 1 | # Contributing to this project 2 | 3 | Please take a moment to review this document in order to make the contribution 4 | process easy and effective for everyone involved. 5 | 6 | Following these guidelines helps to communicate that you respect the time of 7 | the developers managing and developing this open source project. In return, 8 | they should reciprocate that respect in addressing your issue or assessing 9 | patches and features. 10 | 11 | 12 | ## Using the issue tracker 13 | 14 | The issue tracker is the preferred channel for [bug reports](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/contribution/bug-reports.md), 15 | [features requests](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/contribution/feature-requests.md) and [submitting pull 16 | requests](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/contribution/pull-requests.md). 17 | 18 | -------------------------------------------------------------------------------- /wiki/docs/contribution/bug-reports.md: -------------------------------------------------------------------------------- 1 | ## Bug reports 2 | 3 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 4 | Good bug reports are extremely helpful - thank you! 5 | 6 | Guidelines for bug reports: 7 | 8 | 1. **Use the GitHub issue search** — check if the issue has already been 9 | reported. 10 | 11 | 2. **Check if the issue has been fixed** — try to reproduce it using the 12 | latest `main` or development branch in the repository. 13 | 14 | 15 | Example: 16 | 17 | > Short and descriptive example bug report title 18 | > 19 | > A summary of the issue and the browser/OS environment in which it occurs. If 20 | > suitable, include the steps required to reproduce the bug. 21 | > 22 | > 1. This is the first step 23 | > 2. This is the second step 24 | > 3. Further steps, etc. 25 | > 26 | > `` - a link to the reduced test case 27 | > 28 | > Any other information you want to share that is relevant to the issue being 29 | > reported. This might include the lines of code that you have identified as 30 | > causing the bug, and potential solutions (and your opinions on their 31 | > merits). 32 | 33 | -------------------------------------------------------------------------------- /wiki/docs/contribution/feature-requests.md: -------------------------------------------------------------------------------- 1 | 2 | ## Feature requests 3 | 4 | Feature requests are welcome. But take a moment to find out whether your idea 5 | fits with the scope and aims of the project. It's up to *you* to make a strong 6 | case to convince the project's developers of the merits of this feature. Please 7 | provide as much detail and context as possible. 8 | 9 | -------------------------------------------------------------------------------- /wiki/docs/contribution/pull-requests.md: -------------------------------------------------------------------------------- 1 | 2 | ## Pull requests 3 | 4 | Good pull requests - patches, improvements, new features - are a fantastic 5 | help. They should remain focused in scope and avoid containing unrelated 6 | commits. 7 | 8 | **Please ask first** before embarking on any significant pull request (e.g. 9 | implementing features, refactoring code, porting to a different language), 10 | otherwise you risk spending a lot of time working on something that the 11 | project's developers might not want to merge into the project. 12 | 13 | Please adhere to the coding conventions used throughout a project (indentation, 14 | accurate comments, etc.) and any other requirements (such as test coverage). 15 | 16 | Follow this process if you'd like your work considered for inclusion in the 17 | project: 18 | 19 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 20 | and configure the remotes: 21 | 22 | ```bash 23 | # Clone your fork of the repo into the current directory 24 | git clone https://github.com// 25 | # Navigate to the newly cloned directory 26 | cd 27 | # Assign the original repo to a remote called "upstream" 28 | git remote add upstream https://github.com// 29 | ``` 30 | 31 | 2. If you cloned a while ago, get the latest changes from upstream: 32 | 33 | ```bash 34 | git checkout 35 | git pull upstream 36 | ``` 37 | 38 | 3. Create a new topic branch (off the main project development branch) to 39 | contain your feature, change, or fix: 40 | 41 | ```bash 42 | git checkout -b 43 | ``` 44 | 45 | 4. Commit your changes in logical chunks. Please adhere to these [git commit 46 | message guidelines](wiki/git-commits.md) 47 | or your code is unlikely be merged into the main project. Use Git's 48 | [interactive rebase](https://help.github.com/articles/interactive-rebase) 49 | feature to tidy up your commits before making them public. 50 | 51 | 5. Locally merge (or rebase) the upstream development branch into your topic branch: 52 | 53 | ```bash 54 | git pull [--rebase] upstream 55 | ``` 56 | 57 | 6. Push your topic branch up to your fork: 58 | 59 | ```bash 60 | git push origin 61 | ``` 62 | 63 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 64 | with a clear title and description. 65 | 66 | **IMPORTANT**: By submitting a patch, you agree to allow the project owner to 67 | license your work under the same license as that used by the project. 68 | -------------------------------------------------------------------------------- /wiki/docs/coverage.md: -------------------------------------------------------------------------------- 1 | ## Metrics 2 | 3 | ## 1. Sonarcloud Report 4 | ![1](https://user-images.githubusercontent.com/87794374/170926216-42293e7e-54fb-4d3e-bf34-dc108c967327.PNG) 5 | \ 6 | \ 7 | ![2](https://user-images.githubusercontent.com/87794374/170926270-cee56e6c-f43a-4cad-a185-fefa1e702afe.PNG) 8 | 9 | 10 | ### 2. Jest code coverage 11 | ![3](https://user-images.githubusercontent.com/87794374/170926405-0372df37-560b-4bdb-acd4-ffc5cac4c013.PNG) 12 | -------------------------------------------------------------------------------- /wiki/docs/environment/authentication.env.md: -------------------------------------------------------------------------------- 1 | ### ENVIRONMENT VARIABLES USED IN AUTHENTICATION MODULE. 2 | 3 | Given Below is the list of environemnt variables used for the authentication module in our boilerplaate. 4 | 5 | - **SECRET_JWT_KEY:** This variable should be a string value and should be present in the encrypted format. The major role of this environment variable is that it helps in decording the `jwt-token` present in the incoming request. -------------------------------------------------------------------------------- /wiki/docs/environment/crypto.env.md: -------------------------------------------------------------------------------- 1 | ### ENVIRONMENT VARIABLES USED IN ENCRYPTION MODULE. 2 | 3 | The list of environment variables used for the encryption-decryption module in our boilerplate 4 | 5 | - **APPLY_ENCRYPTION:** This variable currently accepts only two values which is `0` and `1`. If this variable is set to `0` then the encyrption-decryption module will not be executed and if it is set to `1` the module is activated and we will get our request and response in encrypted format. 6 | 7 | - **IV:** This environment variable stands for Initialization Vector. The initialization vector is used in the `crypto.ts` file. 8 | 9 | - **SECRET_KEY:** This environemt variable is again used in the `crypto.ts` file. The secret key is useful for creating a cipher code so it should be set accordingly. -------------------------------------------------------------------------------- /wiki/docs/environment/database.env.md: -------------------------------------------------------------------------------- 1 | ### ENVIRONMENT VARIABLES USED IN DATABASE MODULE. 2 | 3 | Here is the list of environment variables that we have used in the `database module`. The variables are as follows. 4 | 5 | - **DB_HOST:** It stands for database host. Here we have to write the IP address of the host in which we will be keeping our database. In simple words it can be defined as a SERVER which will contain a pool of databases. 6 | 7 | 8 | - **DB_DATABASE:** This decides the name of the database which is being hosted at `DB_HOST`. 9 | 10 | 11 | - **DB_USER:** This decides the name of the user used in the MySQL database. It is essential to create a user in MySQL for accessing and managing the databases. 12 | 13 | - **DB_PASSWORD:** This is the password for the `DB_USER`created. If the password is not satisfied then the user will be invalid. 14 | 15 | - **DB_PORT:** This defines the port number where the `DB_DATABASE` is hosted. -------------------------------------------------------------------------------- /wiki/docs/environment/email.env.md: -------------------------------------------------------------------------------- 1 | ### ENVIRONMENT VARIABLES USED FOR THE EMAIL MODULE. 2 | 3 | - For the implementation of the email module we have used the `node-mailer` package and inorder to set its basic configuration we have used the following variables. 4 | 5 | - **EMAIL_HOST:** This should be a string and it should contain the address of the host from which the application will be sending the mail to the users. 6 | 7 | - **EMAIL_PORT:** The port number of the `EMAIL_HOST` should be specified according to the requirment and it should be an integer. 8 | 9 | - **USER_EMAIL:** This should be a string value and this is host mail from which the users will get the email. 10 | 11 | - **USER_PASSWORD:** This is the password of the main user which should be known by admin only. This should not be shared or be visible in any context. It is beneficial that this variable should be present in hashed format. -------------------------------------------------------------------------------- /wiki/docs/environment/environment.md: -------------------------------------------------------------------------------- 1 | ### ENIVRONMENT FILES 2 | 3 | - Environment File as the name suggests is the file in your system that describes your environment. 4 | - Environment File container the **environment variables** which are a fundamental part of developing with Node.js application, allowing your app to behave differently based on the environment you want them to run in. 5 | - In this file we set a variable with value and that you wouldn’t want to share with anyone, purpose of file is keep as secret and secure because in .env file we store our database password, username, API key etc. 6 | - Wherever your app needs configuration, you use environment variables. 7 | 8 | ### Why Do we need Environment Variables 9 | - **Security:** Some things like API keys should not be put in plain text in the code and thereby directly visible. 10 | - **Flexibility:** you can reduce conditional statements à la “If production server then do X else if test server then do Y else if localhost then do Z …”. 11 | - **Adoption:** Popular services like GitLab or Heroku support the usage of environment variables. 12 | 13 | ### How does a .env file looks like 14 | - A `.env` file is more or less a plain text document. It should be placed in the root of your project. Here is an example how a .env file looks like. 15 | 16 | ``` 17 | # API 18 | API_TOKEN=myUniqueApiToken 19 | 20 | # Database 21 | DATABASE_NAME=myDatabaseName 22 | ``` 23 | ### How to get & set environment variables in Node.js 24 | 25 | ``` 26 | // get an environment variable 27 | export const token = process.env['API_TOKEN']; 28 | 29 | // set an environment variable at runtime 30 | process.env['RANDOM_ID'] = Math.random(); 31 | 32 | ``` 33 | ### How to use custom environment variables in Node. 34 | 35 | 1. **Create an .env file:** The file should be placed in the root of your project 36 | 37 | 2. **Install the dotenv library:** `npm install dotenv` 38 | 39 | 3. **Install the dotenv library:** `require('dotenv').config({path: __dirname + '/.env'})` 40 | 41 | 4. Wherever you need to use environment variables (e.g. in GitLab, in Jenkins, in Heroku) you need to add your environment variables. The way depends on the platform but it is usually easy to do. 42 | 43 | 5. Optional: create a function which runs at startup of your server. It should **check whether all the required environment variables are set and throw an error otherwise**. 44 | 45 | ### Nest JS Boilerplate 46 | - In our Nest JS Boilerplate we have currently to environment files. 47 | - `.env` in which we have set the environment variables for our application which will be used in the development mode. 48 | 49 | - `test.env` in this we have set the environment variables for our test environment. 50 | #### Here is the List of the environment variables that we have used in our Nest JS Boilerplate. 51 | 52 | - [Database Module](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/documentation/wiki/docs/environment/database.env.md) 53 | - [Encryption Module](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/documentation/wiki/docs/environment/crypto.env.md) 54 | - [Mailer Module](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/documentation/wiki/docs/environment/email.env.md) 55 | - [Authentication Module](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/documentation/wiki/docs/environment/authentication.env.md) 56 | -------------------------------------------------------------------------------- /wiki/docs/miscellaneous.md: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | 3 | - [Known Issues](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/miscellaneous/known-issues.md) 4 | - [Git commits](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/miscellaneous/git-commits.md) 5 | - [Clean Docker Images](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/miscellaneous/clean-docker.md) 6 | - [Installation Pretteri, Husky & Lint](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/miscellaneous/installation-pretteri-husky-lint.md) -------------------------------------------------------------------------------- /wiki/docs/miscellaneous/clean-docker.md: -------------------------------------------------------------------------------- 1 | ## [Clean Docker Images](https://www.digitalocean.com/community/tutorials/how-to-remove-docker-images-containers-and-volumes) 2 | 3 | ## How to Do a Clean Restart of a Docker Instance 4 | 5 | #### 1. Stop the container(s) using the following command 6 | 7 | `docker-compose down` 8 | 9 | #### 2. Stop and remove all containers 10 | 11 | - List 12 | 13 | `docker ps -a` 14 | 15 | - Remove 16 | 17 | ``` 18 | docker stop $(docker ps -a -q) 19 | docker rm $(docker ps -a -q) 20 | ``` 21 | 22 | #### 4.Remove all images 23 | 24 | - List 25 | 26 | `docker images -a` 27 | 28 | - Remove 29 | 30 | ``` 31 | docker rmi $(docker images -a -q) 32 | ``` 33 | 34 | #### 5. Delete all volumes using the following command 35 | 36 | `docker volume rm $(docker volume ls -q)` 37 | 38 | #### 6. Restart the containers using the following command 39 | 40 | `docker-compose up -d` 41 | 42 | -------------------------------------------------------------------------------- /wiki/docs/miscellaneous/git-commits.md: -------------------------------------------------------------------------------- 1 | # Git commits and pre hooks 2 | 3 | We have added some git pre hooks while committing the code. These pre hooks are executed on every git commit. 4 | 5 | ## What is commitlint 6 | 7 | [Commitlint](https://github.com/conventional-changelog/commitlint) checks if your commit messages meet the [conventional commit format](https://www.conventionalcommits.org/en/v1.0.0/). 8 | 9 | Commit message can be bifurcated into 3 section "(): " 10 | 11 | Type: can be anyone of them feat | bugfix | feature etc. 12 | 13 | > Module/Scope: Module/Scope is the name of the module you are working on 14 | 15 | Commit_Message: Actual commit message 16 | 17 | ``` git commit -m "feat(documentation): adding documentation" ``` 18 | 19 | ### Type 20 | 21 | Must be one of the following: 22 | 23 | - **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) 24 | - **chore**: Updating tasks etc; no production code change 25 | - **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) 26 | - **docs**: Documentation only changes 27 | - **feat**: A new feature 28 | - **fix**: A bug fix 29 | - **perf**: A code change that improves performance 30 | - **refactor**: A code change that neither fixes a bug nor adds a feature 31 | - **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 32 | - **test**: Adding missing tests or correcting existing tests 33 | - **sample**: A change to the samples 34 | -------------------------------------------------------------------------------- /wiki/docs/miscellaneous/installation-pretteri-husky-lint.md: -------------------------------------------------------------------------------- 1 | ### ESLint 2 | `npm install --save-dev eslint eslint-config-prettier eslint-plugin-import` 3 | 4 | [ESLint](https://eslint.org/) is a fully pluggable tool for identifying and reporting on patterns in JavaScript. 5 | 6 | **Configuration file**: [`.eslintrc.js`](https://github.com/smarlhens/nest7-boilerplate/blob/master/.eslintrc.js). 7 | 8 | For more configuration options and details, see the [configuration docs](https://eslint.org/docs/user-guide/configuring). 9 | 10 | 11 | --- 12 | 13 | ### Commitlint 14 | 15 | `npm install --save-dev @commitlint/cli @commitlint/config-conventional commitizen cz-conventional-changelog` 16 | 17 | [commitlint](https://github.com/conventional-changelog/commitlint) checks if your commit messages meet the [conventional commit format](https://conventionalcommits.org). 18 | 19 | **Configuration file**: [`.commitlintrc`] 20 | 21 | In general the pattern mostly looks like this: 22 | 23 | ```sh 24 | type(scope?): subject #scope is optional 25 | ``` 26 | 27 | Are you a good `commitizen` ? 28 | 29 | --- 30 | 31 | 32 | ### Husky 33 | 34 | `npm install --save-dev husky lint-staged prettier` 35 | 36 | [Husky](https://github.com/typicode/husky) is a package that helps you create Git hooks easily. 37 | 38 | --- 39 | -------------------------------------------------------------------------------- /wiki/docs/miscellaneous/known-issues.md: -------------------------------------------------------------------------------- 1 | # How to fix “mbind: Operation not permitted” in mysql error log 2 | 3 | Add the capability CAP_SYS_NICE to your container until MySQL server can handle the error itself "silently". 4 | 5 | ``` 6 | service: 7 | mysql: 8 | image: mysql:8.0.15 9 | # ... 10 | cap_add: 11 | - SYS_NICE # CAP_SYS_NICE 12 | ``` 13 | [https://stackoverflow.com/questions/55559386/how-to-fix-mbind-operation-not-permitted-in-mysql-error-log](https://stackoverflow.com/questions/55559386/how-to-fix-mbind-operation-not-permitted-in-mysql-error-log) 14 | [https://docs.docker.com/config/containers/resource_constraints/](https://docs.docker.com/config/containers/resource_constraints/) 15 | 16 | # MySQL init script on Docker compose 17 | 18 | MySQL Docker container supports executing init scripts on startup, All we need to do is either copy or mount our script files to /docker-entrypoint-initdb.d/ folder inside MySQL docker image. 19 | 20 | ``` 21 | # Docker Compose with MySQL Server 22 | version: '3.3' 23 | services: 24 | database: 25 | image: 'mysql:8' 26 | cap_add: 27 | - SYS_NICE 28 | container_name: '${APP_NAME}_mysql' 29 | hostname: '${APP_NAME}_mysql' 30 | networks: 31 | - internal 32 | ports: 33 | - '127.0.0.1:${DB_PORT}:3306' 34 | volumes: 35 | - ./docker/init.sql:/docker-entrypoint-initdb.d/init.sql 36 | - mysql:/var/lib/mysql 37 | env_file: 38 | - .env 39 | ``` 40 | 41 | # The "class-validator" package is missing. Please, make sure to install this library ($ npm install class-validator) to take advantage of ValidationPipe 42 | 43 | `npm install --save class-validator` 44 | 45 | # The "class-transformer" package is missing. Please, make sure to install this library ($ npm install class-transformer) to take advantage of ValidationPipe. 46 | 47 | `npm install class-transformer --save ` 48 | 49 | # Map src directory in package.json jest config 50 | 51 | [https://github.com/nestjs/nest/issues/4953](https://github.com/nestjs/nest/issues/4953) 52 | 53 | # Error: Interface 'Response' incorrectly extends interface 'Response' 54 | 55 | `npm i -D @types/express-serve-static-core` 56 | 57 | [https://stackoverflow.com/questions/63330265/getting-interface-responseresbody-incorrectly-extends-interface-response](https://stackoverflow.com/questions/63330265/getting-interface-responseresbody-incorrectly-extends-interface-response) 58 | [https://github.com/DefinitelyTyped/DefinitelyTyped/issues/46639](https://github.com/DefinitelyTyped/DefinitelyTyped/issues/46639) 59 | 60 | # Is there a recommended way to update NestJS? 61 | 62 | ``` 63 | $ npm install -g @nestjs/cli 64 | $ nest update 65 | ``` 66 | 67 | `npm install --save @nestjs/common@latest @nestjs/config@latest @nestjs/core@latest @nestjs/platform-express@latest @nestjs/typeorm@latest reflect-metadata rxjs` 68 | 69 | `npm install --save-dev @nestjs/testing` 70 | [https://stackoverflow.com/questions/57469252/is-there-a-recommended-way-to-update-nestjs](https://stackoverflow.com/questions/57469252/is-there-a-recommended-way-to-update-nestjs) -------------------------------------------------------------------------------- /wiki/docs/modules.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | Boiler plate is build over the top of [Nest](https://github.com/nestjs/nest) with structure of /src/core module which consist all the major functionality required in the development like database connection, SMTP integration, Email template management, request response handler, Logger etc. 4 | 5 | - [Home](/) 6 | - [Logger](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/modules/logger.md) 7 | - [Request Response](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/modules/request-response.md) 8 | - [Mailer](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/modules/mailer.md) 9 | - [Database](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/modules/database.md) 10 | - [Pattern](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/modules/pattern.md) 11 | - [Crypto](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/modules/crypto.md) 12 | - [Swagger/Open API](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/modules/swagger.md) 13 | -------------------------------------------------------------------------------- /wiki/docs/modules/authentication.md: -------------------------------------------------------------------------------- 1 | # AUTHENTICATION MODULE 2 | 3 | ## INTORODUCTION 4 | - Authentication is an `essential` part of most applications. 5 | - It helps us to secure our application from the third party viewers. 6 | - There are many different approaches and strategies to handle authentication. 7 | --- 8 | ## PASSPORT LIBRARY 9 | 10 | - For creating a authentication module in our boilerplate we will be using the library called `passport`. 11 | - [Passport]('https://github.com/jaredhanson/passport') is the most popular node.js authentication library, well-known by the community and successfully used in many production applications. 12 | - It's straightforward to integrate this library with a Nest application using the `@nestjs/passport` module. 13 | 14 | ## IMPLEMENTATION 15 | 16 | - The method that we are using for authentication is the **`NestJS JWT Authentication`** using `Passport Strategy`. 17 | - `JWT` stands for `JSON WEB TOKENS`. Basically, these tokens are issued by the server after user authentication and can be used for further requests as long as the token is valid. 18 | - The two high-level requirements for our `NestJS JWT Authentication` is as follows: 19 | - Let the users authenticate with a username/password. Upon authentication, return a JWT. This JWT can be used for subsequent calls to the application. 20 | - Once the JWT token is generated then that token will be used in the hear of each request as a `Bearer Token` that will validate each API call. 21 | - See below illustration that explains the concept. 22 | 23 | ![Selection_179](https://user-images.githubusercontent.com/87708447/168235678-fd32d584-84f7-414c-baf3-d4b7a71e82c5.png) 24 | --- 25 | 26 | ## INSTALLATION OF THE PACKAGES 27 | 28 | To get started with NestJS JWT, we need to install the below package. 29 | > npm install --save @nestjs/jwt passport-jwt 30 | > npm install --save-dev @types/passport-jwt 31 | > npm i --save @nestjs/passport passport 32 | 33 | - The `@nestjs/jwt` package helps with JWT manipulation. 34 | - The `passport-jwt package` implements the JWT strategy. Also, the `@types/passport-jwt` package provides the type definitions to make development easy. 35 | 36 | - Once the installation is done we need to create a Authentication module which is done by creating `auth` folder inside the `src` directory. 37 | - In that folder we will be creating `auth.module.ts`, `auth.controller.ts`, `auth.service.ts` which are the essential files. 38 | 39 | ## STEP 1 :GENERATING THE JWT 40 | 41 | - The first step is for us to be able to generate a JWT and return it as response of the `auth/login` route inside `auth.controller.ts`. 42 | ``` 43 | async generateToken(@Req() req: Request, @Res() res: Response): Promise { 44 | try { 45 | const user: ValidateUserDto = req.body; 46 | const resWithAcessToken = await this.authService.generateToken(user); 47 | return res.success(resWithAcessToken, StatusCodes.OK); 48 | } catch (e) { 49 | return res.error(e); 50 | } 51 | } 52 | ``` 53 | - The `AuthService` class looks like: 54 | 55 | ``` 56 | async generateToken(user: ValidateUserDto): Promise { 57 | try { 58 | const userData = await this.findUserByEmail(user); 59 | const isMatched = await comparePassword(user.password, userData.password); 60 | if(isMatched) { 61 | const payload = `${userData.firstName}${userData.id}`; 62 | const accessToken = this.jwtService.sign(payload); 63 | return { 64 | access_token: accessToken, 65 | }; 66 | } else { 67 | throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); 68 | } 69 | } catch (e) { 70 | throw e; 71 | } 72 | } 73 | ``` 74 | - The `generateToken` method of the `AuthService` class created the JWT after the user credentials are validated. If the credentials are not validated then error will be thrown. 75 | - The `sign` method from `@nestjs/jwt` generates the token. 76 | - The token which is generated will be in the below format: 77 | 78 | - Image 79 | 80 | ## STEP 2: CONFIGURATION INSIDE AUTH MODULE. 81 | 82 | - The configuration inside the `auth.module.ts` is as follows. 83 | ``` 84 | @Module({ 85 | imports: [ 86 | UsersModule, 87 | JwtModule.registerAsync({ 88 | imports: [ConfigModule], 89 | useFactory: async (config: ConfigService) => { 90 | return { 91 | secret: config.get('SECRET_JWT_KEY'), 92 | }; 93 | }, 94 | inject: [ConfigService], 95 | }), 96 | ConfigModule, 97 | PassportModule, 98 | ], 99 | providers: [AuthService, JwtStrategy], 100 | controllers: [AuthController], 101 | // exports:[AuthService] 102 | }) 103 | export class AuthModule {} 104 | ``` 105 | - Here, we are basically registering the `JwtModule`. While doing so, we specify the `secret` key. 106 | - The value of the secret key is been specified in the `.env` file. 107 | 108 | ## STEP 3: IMPLEMENTING THE JWT PASSPORT STRATEGY 109 | 110 | - We need to do this to be able to address our second requirement of protecting endpoints using JWT. 111 | - We will create a new file `jwt.strategy.ts` within the auth folder and place the below code. 112 | 113 | ``` 114 | import { Injectable } from '@nestjs/common'; 115 | import { ConfigService } from '@nestjs/config'; 116 | import { PassportStrategy } from '@nestjs/passport'; 117 | import { ExtractJwt, Strategy } from 'passport-jwt'; 118 | 119 | @Injectable() 120 | export class JwtStrategy extends PassportStrategy(Strategy) { 121 | constructor(private config: ConfigService) { 122 | super({ 123 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 124 | ignoreExpiration: false, 125 | secretOrKey: config.get('auth.secretKey') 126 | }); 127 | } 128 | 129 | async validate(payload: any) { 130 | return { 131 | firstName: payload.firstName, 132 | id: payload.id, 133 | }; 134 | } 135 | } 136 | 137 | ``` 138 | - Basically, here we extend the `PassportStrategy` class and specify that we want to extend the Strategy from `passport-jwt` package. 139 | - Next, we initialize the strategy by passing some parameters in the `super() call`. Below are the parameters in detail: 140 | - **jwtFromRequest:** This parameter specifies the method using which we will extract the JWT from the request. Basically, we use the `fromAuthHeaderAsBearerToken()` method from the `ExtractJwt` package. This is because it is standard practice to pass the JWT token as the bearer token in the authorization header while making API requests. 141 | 142 | - **ignoreExpiration:** We set this property as false. This basically means that we want to block requests with expired tokens. If a call is made with an expired token, our application will return 401 or Unauthorized response. 143 | 144 | - **secretOrKey:** This is basically the secret key from the `.env` file. Ideally, this should pem-encoded publish key. Also, it should not be exposed publicly. 145 | 146 | - Next, we implement the validate() function in which we simply return the user object. 147 | - This might seem confusing as we are not processing anything in the function. 148 | > This is because for the JWT strategy, passport first verifies the signature of the JWT and decodes the JSON object. Only then does it actually call the validate() function and passes the decoded JSON as payload. 149 | - Basically, this ensures that if the validate() function is called, it means the JWT is also valid 150 | 151 | ## STEP 4 CREATING AUTH GUARD. 152 | - Finally, we create an Auth Guard with the file name `jwt.auth.guard.ts` that uses the JWT strategy. 153 | 154 | ``` 155 | import { Injectable } from '@nestjs/common'; 156 | import { AuthGuard } from '@nestjs/passport'; 157 | 158 | @Injectable() 159 | export class JwtAuthGuard extends AuthGuard('jwt') {} 160 | 161 | ``` 162 | - Our JwtAuthGuard extends the AuthGuard provided by the `@nestjs/passport` package. 163 | 164 | ## STEP 5 PROTECTING THE APIS 165 | 166 | - Now, we can simply use this guard in one of the routes that we want to protect using the **UseGuards()** decotrator. 167 | 168 | ``` 169 | @UseGuards(JwtAuthGuard) 170 | @Get(':id') 171 | @ApiOkResponse({ description: apiResponse.apiUserGetById }) 172 | @ApiBearerAuth('JWT-auth') 173 | async getUserById(@Req() req: Request, @Res() res: Response, @Param('id') id: string): Promise { 174 | try { 175 | const userById = await this.usersService.findOne(id); 176 | return res.success(userById, StatusCodes.OK); 177 | } catch (e) { 178 | return res.error(e); 179 | } 180 | } 181 | 182 | ``` 183 | - Here, the request handler getUserById() uses the JwtAuthGuard. Basically, this means that unless there is a valid JWT in the Authorization header of the incoming HTTP request, the endpoint will not provide a valid response. 184 | --- 185 | ## OUTPUT WILL BE AS FOLLOWS. 186 | 187 | - **CASE 1:** If the token is not entered and the API is being accessed then following will be the output. 188 | 189 | ![Selection_181](https://user-images.githubusercontent.com/87708447/168236333-be25a152-84e9-42bf-b0eb-3c07cc18c667.png) 190 | 191 | - **CASE 2:** If the user credentials are not valid. 192 | 193 | ![Selection_182](https://user-images.githubusercontent.com/87708447/168236606-39998a73-88d3-4bad-9d03-723640cf197e.png) 194 | 195 | - **CASE 3:** If the credentials are valid then the access token is generated. 196 | 197 | ![Selection_183](https://user-images.githubusercontent.com/87708447/168236814-3aa62556-9f24-4e1f-8f93-e1c9e03e3978.png) 198 | 199 | 200 | -------------------------------------------------------------------------------- /wiki/docs/modules/compression.md: -------------------------------------------------------------------------------- 1 | # Compression 2 | 3 | We have used this [middleware](http://expressjs.com/en/resources/middleware/compression.html) library in order to compress the response. 4 | 5 | The majority of the browsers support 3 type of encodings gzip, deflate and br, and it’s almost always sent by the browser for any request sent to the servers using the `Accept-Encoding` request header, but it’s up to the server if they want to encoder the response and by doing so they need to communicate which encoding they’re using to encode using the `Content-Encoding` response header. 6 | 7 | The objective of Compression 8 | 9 | 1) Compression can greatly decrease the size of the response body, thereby increasing the speed of a web app. By using this compression, we can improve the performance of our Node.js applications as our payload size is dramatically reduced above 70%. 10 | 2) The following compression codings are supported: 11 | - deflate 12 | - gzip 13 | 3) Gzip (gzip): The most widely used compression format for server and client interactions. It is based on the Deflate algorithm and is compatible with all current browsers 14 | 15 | ## Install 16 | 17 | This is a [Node.js](https://nodejs.org/en/) module available through the 18 | [npm registry](https://www.npmjs.com/). Installation is done using the 19 | [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): 20 | 21 | ```bash 22 | $ npm install compression 23 | ``` 24 | 25 | ## Using compression middleware 26 | ``` 27 | app.use(compression({ 28 | filter: shouldCompress, 29 | threshold: 0 30 | })); 31 | ``` 32 | 33 | `filter` decide if the answer should be compressed or not, depending on the 'shouldCompress' function 34 | `threshold` means the byte threshold for the response body size before considering compression, the default is 1 kB. Currently the threshold is set to 0 bytes which means every response body is going to be compressed. We can tweak this parameter according to our convenience. 35 | 36 | > When compression middleware is not used, response body size is 4.3kB 37 | 38 | ![Console](https://github.com/ssingh3006/rest-node-nestjs/blob/newFeatures/wiki/images/uncompressed.PNG?raw=true) 39 | 40 | > When compression middleware is used, response body size reduces to 1.3kB. We can also see response header **`Content-Encoding`** is set to **`gzip`** 41 | 42 | ![Console](https://github.com/ssingh3006/rest-node-nestjs/blob/newFeatures/wiki/images/compressed.PNG?raw=true) 43 | 44 | 45 | For a complete list of properties to choose from, see the documentation [here](http://expressjs.com/en/resources/middleware/compression.html). 46 | 47 | -------------------------------------------------------------------------------- /wiki/docs/modules/cors.md: -------------------------------------------------------------------------------- 1 | # CORS 2 | 3 | Cross-origin resource sharing (CORS) is a mechanism that allows resources to be requested from another domain. Under the hood, Nest makes use of the Express [cors](https://github.com/expressjs/cors) package. This package provides various options that you can customize based on your requirements.The CORS mechanism supports secure cross-origin requests and data transfers between browsers and servers. 4 | 5 | ## Installation 6 | ``` 7 | $ npm install cors 8 | ``` 9 | 10 | ## Simple Usage (Enable *All* CORS Requests) 11 | ``` 12 | # bootstrap.ts 13 | import cors from 'cors'; 14 | import { corsOptions } from './CORS/cors.config'; 15 | 16 | app.use(cors(corsOptions)); 17 | ``` 18 | 19 | `corsOptions` is used to set the configuration options of cors middleware. Here we have set origin option to enable Access-Control-Allow-Origin CORS header. 20 | 21 | ``` 22 | # cors.config.ts 23 | export const corsOptions = { 24 | origin: ['http://example1.com', 'http://example2.com','http://127.0.0.1:5500'], 25 | } 26 | ``` 27 | ## Using fetch API to check CORS 28 | We can make a fetch request(by writing a simple HTML file) to our server to check whether our CORS configuration are working properly 29 | 30 | ```html 31 | 32 | 33 | 34 | 35 | 36 | Checking CORS 37 | 38 | 39 | 44 | 45 | 46 | 47 | ``` 48 | The result that we got are shown below 49 | 50 | > Here we can see the response in the console 51 | 52 | ![Console](https://github.com/ssingh3006/rest-node-nestjs/blob/newFeatures/wiki/images/CORS-console-result.PNG?raw=true) 53 | 54 | > Here we can see that the responnse header `Access-Control-Allow-Origin` is set to the origin from where we are making fetch request 55 | 56 | ![Response Headers](https://github.com/ssingh3006/rest-node-nestjs/blob/newFeatures/wiki/images/CORS-response-headers.PNG?raw=true) 57 | 58 | 59 | ## Configuration Options 60 | 61 | * `origin`: Configures the **Access-Control-Allow-Origin** CORS header. Possible values: 62 | - `Boolean` - set `origin` to `true` to reflect the [request origin](http://tools.ietf.org/html/draft-abarth-origin-09), as defined by `req.header('Origin')`, or set it to `false` to disable CORS. 63 | - `String` - set `origin` to a specific origin. For example if you set it to `"http://example.com"` only requests from "http://example.com" will be allowed. 64 | - `RegExp` - set `origin` to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin will be reflected. For example the pattern `/example\.com$/` will reflect any request that is coming from an origin ending with "example.com". 65 | - `Array` - set `origin` to an array of valid origins. Each origin can be a `String` or a `RegExp`. For example `["http://example1.com", /\.example2\.com$/]` will accept any request from "http://example1.com" or from a subdomain of "example2.com". 66 | - `Function` - set `origin` to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback (called as `callback(err, origin)`, where `origin` is a non-function value of the `origin` option) as the second. 67 | * `methods`: Configures the **Access-Control-Allow-Methods** CORS header. Expects a comma-delimited string (ex: 'GET,PUT,POST') or an array (ex: `['GET', 'PUT', 'POST']`). 68 | * `allowedHeaders`: Configures the **Access-Control-Allow-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Type,Authorization') or an array (ex: `['Content-Type', 'Authorization']`). If not specified, defaults to reflecting the headers specified in the request's **Access-Control-Request-Headers** header. 69 | * `exposedHeaders`: Configures the **Access-Control-Expose-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range') or an array (ex: `['Content-Range', 'X-Content-Range']`). If not specified, no custom headers are exposed. 70 | * `credentials`: Configures the **Access-Control-Allow-Credentials** CORS header. Set to `true` to pass the header, otherwise it is omitted. 71 | * `maxAge`: Configures the **Access-Control-Max-Age** CORS header. Set to an integer to pass the header, otherwise it is omitted. 72 | * `preflightContinue`: Pass the CORS preflight response to the next handler. 73 | * `optionsSuccessStatus`: Provides a status code to use for successful `OPTIONS` requests, since some legacy browsers (IE11, various SmartTVs) choke on `204`. 74 | 75 | The default configuration is the equivalent of: 76 | 77 | ```json 78 | { 79 | "origin": "*", 80 | "methods": "GET,HEAD,PUT,PATCH,POST,DELETE", 81 | "preflightContinue": false, 82 | "optionsSuccessStatus": 204 83 | } 84 | ``` -------------------------------------------------------------------------------- /wiki/docs/modules/crypto.md: -------------------------------------------------------------------------------- 1 | # Encryption-Decryption 2 | 3 | Encryption-Decryption module is present in the Boiler Plate. We have used the [crypto](https://nodejs.org/api/crypto.html) library in order to implement the encryption decryption functionality. 4 | 5 | The objective of Encryption-Decryption module 6 | 7 | 1) This module would support sending the requests to the server from the user in encrypted form and also encryptingthe reponses that the user will receive from the server. 8 | 2) We need to provide a flag variable which tells us whether we need to encrypt the data or not in the .env file. 9 | 3) Also we need to define the constants such as which algorithm to be used for encryption in the crypto.ts file. 10 | 4) Currently we are using **aes-256-gcm** algorithm for encryption and decryption. 11 | 12 | # Configuration 13 | 14 | To start the encryption-decryption service we need to set the APPLY_ENCRYPTION variable to 1 in the .env file. 15 | ``` 16 | APPLY_ENCRYPTION= 17 | ``` 18 | 19 | We can also set these constants according to our preference in the crypto.ts file. 20 | ``` 21 | const inputEncoding= 22 | const outputEncoding= 23 | const salt= 24 | const algorithm= 25 | const password= 26 | ``` 27 | ## Usability 28 | Since we require to encrypt the request and responses of the application, Encryption-Decryption service is being included in the core module. 29 | 30 | 31 | ### [What is the main difference between a key, an IV and a nonce?](https://crypto.stackexchange.com/questions/3965/what-is-the-main-difference-between-a-key-an-iv-and-a-nonce) 32 | 33 | A key, in the context of symmetric cryptography, is something you keep secret. Anyone who knows your key (or can guess it) can decrypt any data you've encrypted with it (or forge any authentication codes you've calculated with it, etc.). 34 | 35 | An IV or initialization vector is, in its broadest sense, just the initial value used to start some iterated process. The term is used in a couple of different contexts and implies different security requirements in each of them. 36 | 37 | A nonce, in the broad sense, is just "a number used only once". The only thing generally demanded of a nonce is that it should never be used twice (within the relevant scope, such as encryption with a particular key). The unique IVs used for block cipher encryption qualify as nonces, but various other cryptographic schemes make use of nonces as well. 38 | 39 | ### [Why does the Signal protocol use AES/CBC instead of AES/GCM?](https://crypto.stackexchange.com/questions/68163/why-does-the-signal-protocol-use-aes-cbc-instead-of-aes-gcm) 40 | ### [AES-GCM Disadvantage](https://crypto.stackexchange.com/questions/18420/aes-gcm-disadvantage) 41 | ### [Why would I ever use AES-256-CBC if AES-256-GCM is more secure?]( https://security.stackexchange.com/questions/184305/why-would-i-ever-use-aes-256-cbc-if-aes-256-gcm-is-more-secure) 42 | ### [How to choose an Authenticated Encryption mode](https://blog.cryptographyengineering.com/2012/05/19/how-to-choose-authenticated-encryption/) 43 | ### [Node.js - AES Encryption/Decryption with AES-256-GCM using random Initialization Vector + Salt](https://gist.github.com/AndiDittrich/4629e7db04819244e843) 44 | 45 | ### Encryption algorithms should be used with secure mode and padding scheme 46 | 47 | - Encryption operation mode and the padding scheme should be chosen appropriately to guarantee data confidentiality, integrity and authenticity: 48 | - For block cipher encryption algorithms (like AES): 49 | - The GCM (Galois Counter Mode) mode which works internally with zero/no padding scheme, is recommended, as it is designed to provide both data authenticity (integrity) and confidentiality. Other similar modes are CCM, CWC, EAX, IAPM and OCB. 50 | - The CBC (Cipher Block Chaining) mode by itself provides only data confidentiality, it’s recommended to use it along with Message Authentication Code or similar to achieve data authenticity (integrity) too and thus to prevent padding oracle attacks. 51 | - The ECB (Electronic Codebook) mode doesn’t provide serious message confidentiality: under a given key any given plaintext block always gets encrypted to the same ciphertext block. This mode should not be used. 52 | - For RSA encryption algorithm, the recommended padding scheme is OAEP. 53 | 54 | - Noncompliant Code Example 55 | - Noncompliant: CBC with PKCS5/7 (set by default) is vulnerable to oracle padding attacks 56 | 57 | `crypto.createCipheriv("AES-128-CBC", key, iv);` 58 | 59 | - Noncompliant: ECB doesn't provide serious message confidentiality 60 | 61 | `crypto.createCipheriv("AES-128-ECB", key, "");` 62 | 63 | - Compliant Solution 64 | 65 | `crypto.createCipheriv("AES-256-GCM", key, iv);` 66 | 67 | ### Test 68 | 69 | [https://www.devglan.com/online-tools/aes-encryption-decryption](https://www.devglan.com/online-tools/aes-encryption-decryption) 70 | -------------------------------------------------------------------------------- /wiki/docs/modules/database.md: -------------------------------------------------------------------------------- 1 | # Database 2 | 3 | Boiler plate has already included mysql and mongodb database configuration within the core modules. To leverage the functionality, we need to define the database connection in database.module.ts which is in db folder inside src. Update the database configuration as per your setting. 4 | 5 | ### Installation 6 | For integrating with SQL and NoSQL databases, Nest provides the [@nestjs/typeorm](https://github.com/nestjs/typeorm) package. Nest uses [TypeORM](https://github.com/nestjs/typeorm) because it's the most mature Object Relational Mapper (ORM) available for TypeScript. Since it's written in TypeScript, it integrates well with the Nest framework.TypeORM provides support for many relational databases, such as PostgreSQL, Oracle, Microsoft SQL Server, SQLite, and even NoSQL databases like MongoDB. 7 | 8 | ``` 9 | $ npm install --save @nestjs/typeorm typeorm 10 | ``` 11 | Now proceed to install the associated client API libraries for selected database. 12 | For MySQL install [mysql](https://www.npmjs.com/package/mysql2) 13 | ``` 14 | $ npm install --save mysql2 15 | ``` 16 | For MongoDB install [MongoDB NodeJS Driver](https://www.npmjs.com/package/mongodb).We install older version as typeorm does not [support mongodb driver for v4](https://github.com/typeorm/typeorm/issues/7907) 17 | ``` 18 | $ npm install --save mongodb@3.7.0 19 | ``` 20 | > **Note:** It is a best practice to import database information from application configuration. 21 | 22 | ### Configuration 23 | ```ts 24 | # database.module.ts 25 | 26 | @Module({ 27 | imports: [ 28 | TypeOrmModule.forRootAsync({ 29 | name: 'mysql_connection', 30 | imports: [ConfigModule], 31 | inject: [ConfigService], 32 | useFactory: (config: ConfigService) => ({ 33 | type: 'mysql', 34 | host: config.get('db.host'), 35 | port: config.get('db.port1'), 36 | username: config.get('db.username'), 37 | password: config.get('db.password'), 38 | database: config.get('db.database'), 39 | entities: [User], 40 | // entities: [__dirname + '/**/*.entity{.ts,.js}'], 41 | synchronize: config.get('app.env') === 'local' || 'dev' ? true : false, 42 | } 43 | ), 44 | }), 45 | TypeOrmModule.forRootAsync({ 46 | name: 'mongoDB_connection', 47 | imports: [ConfigModule], 48 | inject: [ConfigService], 49 | useFactory: (config: ConfigService) => ({ 50 | type: 'mongodb', 51 | host: config.get('db.host'), 52 | port: config.get('db.port2'), 53 | username: config.get('db.username'), 54 | password: config.get('db.password'), 55 | database: config.get('db.database'), 56 | useUnifiedTopology: true, 57 | authSource: 'admin', 58 | entities: [User], 59 | synchronize: config.get('app.env') === 'local' || 'dev' ? true : false, 60 | }), 61 | }), 62 | ], 63 | }) 64 | ``` 65 | 66 | `type: ` Database type. Must specify which database engine to use. 67 | `host: ` Database host. 68 | `port: ` Database host port. Default mysql port is 3306 & default mongodb port is 27017. 69 | `username: ` Database username. 70 | `password: ` Database password. 71 | `database: ` Database name. 72 | `useUnifiedTopology: ` Determines whether or not to use the new Server Discovery and Monitoring engine. 73 | `authSource: ` If the Database authentication is dependent on another databaseName 74 | `entities: ` Entities that maps with database table. It is shown below 75 | ```ts 76 | # users.entity.ts 77 | 78 | @Entity() 79 | export class User { 80 | @PrimaryGeneratedColumn() 81 | @ObjectIdColumn() 82 | id: string; 83 | 84 | @Column() 85 | firstName: string; 86 | 87 | @Column() 88 | lastName: string; 89 | 90 | @Column({ default: true }) 91 | isActive: boolean; 92 | } 93 | ``` 94 | `synchronize: ` Synchronize the entities with databases. Should not be enabled for production. 95 | 96 | > Using the `forFeature()` method repositories are registered in the current scope. 97 | ```ts 98 | # users.module.ts 99 | 100 | @Module({ 101 | imports: [TypeOrmModule.forFeature([UserDbRepository],'mongoDB_connection')], 102 | providers: [UsersService], 103 | controllers: [UsersController], 104 | }) 105 | ``` 106 | 107 | > Inject UserDbRepository using `@InjectRepository` decorator 108 | ```ts 109 | # users.service.ts 110 | 111 | constructor( 112 | @InjectRepository(UserDbRepository,'mongoDB_connection') 113 | private readonly usersRepository: UserRepository 114 | ) {} 115 | ``` 116 | ### Using other database apart from MySQL and MongoDb. 117 | 118 | The existing boiler plate comes with by default support for MySQL. This doesn't mean that you cannot use any other database like PostgreSQL, Oracle, Microsoft SQL Server etc. Install the respective package of the database and update the config.ts as per required database configuration. 119 | 120 |
121 | 122 | ## NoSQL injections 123 | 124 | A NoSQL injection vulnerability is an error in a web application that uses a NoSQL database. This web application security issue lets a malicious party bypass authentication, extract data, modify data, or even gain complete control over the application. NoSQL injection attacks are the result of a lack of data sanitization. 125 | 126 | NoSQL injections are just one of many injection attacks, similar to traditional SQL Injections. They are engineered to exploit modern databases that do not use SQL. While NoSQL database engines have a different structure and do not support SQL statements and SQL queries, they still let users perform queries. They do not support one standardized language and therefore the query language is dependent on the implementation: database (e.g. MongoDB), language, and framework (e.g. Node.js, Angular). However, NoSQL queries are most often based on JSON and they can include user input. If this input is not sanitized, they are vulnerable to injections. 127 | 128 | ![NoSQL Injection](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/nosql_support/wiki/images/nosql-query-injection.png?raw=true) 129 | 130 | 131 | ### Types Of Injection Attacks 132 | 133 | 1) **In-band Injections** : In-band Injection is the most common and easy-to-exploit of Injection attacks. In-band Injection occurs when an attacker is able to use the same communication channel to both launch the attack and gather results. As an example, an attacker may use the HTTP communication deploy the attack to a backend and get the results on the same channel 134 | 135 | 2) **Inferential Injection (Blind Injection)**: In an inferential attack, no data is actually transferred via the web application and the attacker would not be able to see the result of an attack in-band. Instead, an attacker is able to reconstruct the database structure by sending payloads, observing the web application’s response and the resulting behavior of the database server. 136 | Blind injection is nearly identical to normal injection, the only difference being the way the data is retrieved from the database. When the database does not output data to the web page, an attacker is forced to steal data by asking the database a series of true or false questions. This makes exploiting the Injection vulnerability more difficult, but not impossible. 137 | 138 | 3) **Out-of-band Injections**: This not very common type of injection, mostly because it depends on features being enabled on the database server being used by the web application. Out-of-band Injection occurs when an attacker is unable to use the same channel to launch the attack and gather results. 139 | 140 | ### How to prevent injection attacks 141 | 142 | - Try to avoid building queries from strings, use safe APIs and prepared statements. 143 | - Validate input to detect malicious values, also verify the types of input data i.e. string, number, Boolean, object etc. We can use joi or any other tool for this. 144 | - To minimize the potential damage of a successful injection attack, do not assign DBA or admin type access rights to your application accounts, we can create new roles with specific/limited access. 145 | - Sanitize the data, we can use express-mongo-sanitize to sanitize incoming data for express mongoDB. 146 | ## Database Vulnerabilty against injection 147 | 148 | | Connection type | With ORM | Without ORM | 149 | | ------------ | ---------- | -------------- | 150 | | Security | Risk is low

Data sanitization not required

Generates "parameterized statements" | Risk is high

Data sanitization required

"parameterized statements" needs to written manually | 151 | 152 | TypeORM provide many ways to construct a database query, but they also give you the option to execute 'raw' queries as a string.Also they allow to write some part of a generated query as a raw string. This should to be avoided, as it defeats the purpose of using an ORM. 153 | 154 | In that scenario, simply injecting user input variables into a string query (instead of using the ORM's core API / query builder functions) puts data security at a higher risk. 155 | 156 | ### Data sanitization for MongoDB 157 | 158 | We can use [express-mongo-sanitize](https://www.npmjs.com/package/express-mongo-sanitize) for this purpose,if required.This module searches for any keys in objects that begin with a `$` sign or contain a `.`, from req.body, req.query or req.params. It can then either: 159 | 160 | - completely remove these keys and associated data from the object, or 161 | - replace the prohibited characters with another allowed character. 162 | 163 | Object keys starting with a $ or containing a . are reserved for use by MongoDB as operators. Without this sanitization, malicious users could send an object containing a $ operator, or including a ., which could change the context of a database operation. Most notorious is the $where operator, which can execute arbitrary JavaScript on the database.The best way to prevent this is to sanitize the received data, and remove any offending keys, or replace the characters with a 'safe' one. 164 | > Simple add the the middleware after installing the library as shown below 165 | ```ts 166 | # bootsrap.ts 167 | ... 168 | import mongoSanitize from 'express-mongo-sanitize'; 169 | 170 | ... 171 | app.use(mongoSanitize()); 172 | ... 173 | ``` -------------------------------------------------------------------------------- /wiki/docs/modules/database.migration.md: -------------------------------------------------------------------------------- 1 | # Database Migration 2 | 3 | Database migration can be defined as the process of migrating data from one source database to anoither targeted database by using the data migration service. 4 | In our boilerplate we are implementing this module so that the user cannot lose its data from the database even after there are some changes made in the relational schema of the database. 5 | 6 | > The concept of Database migration is executed only when the application is running in the production environment. 7 | > This method is recommended by TypeORM itself in order to avoid loss of data. 8 | 9 | ## Implementation 10 | - We will be implementing Database Migration using the TypeORM migration. 11 | - Migrations are scripts that, when executed in sequence, bring the database into the same state as the application, so that the database schema matches the application’s data models. 12 | - Migrations are created and run after changing the applications data models; for example, after creating or changing Entities in NestJS. 13 | 14 | ## Prequisites 15 | - [NestJs](https://docs.nestjs.com/) should be installed. 16 | - [TypeORM](https://docs.nestjs.com/techniques/database) should be installed. 17 | 18 | ## Usage 19 | 1. First create a filename named `typeorm.config.ts` with the following configuration. 20 | ``` 21 | const config: ConnectionOptions = { 22 | type: 'mysql', 23 | host: process.env.DB_HOST, 24 | port: +process.env.DB_PORT, 25 | username: process.env.DB_USER, 26 | password: process.env.DB_PASSWORD, 27 | database: process.env.DB_DATABASE, 28 | entities: [User], 29 | synchronize: false, 30 | cli: { 31 | migrationsDir: 'src/db/migrations', 32 | }, 33 | }; 34 | export = config; 35 | ``` 36 | > Remember to keep `synchronize:false` 37 | 38 | Then we have to make changes in the `src/db/database.module.ts` 39 | ``` 40 | @Module({ 41 | imports: [ 42 | TypeOrmModule.forRootAsync({ 43 | imports: [ConfigModule], 44 | inject: [ConfigService], 45 | useFactory: (config: ConfigService) => ({ 46 | type: 'mysql', 47 | host: config.get('db.host'), 48 | port: config.get('db.port'), 49 | username: config.get('db.username'), 50 | password: config.get('db.password'), 51 | database: config.get('db.database'), 52 | entities: [User], 53 | // entities: [__dirname + '/**/*.entity{.ts,.js}'], 54 | synchronize: config.get('app.env') === 'local' || 'dev' ? true : false, 55 | migrations: ['dist/db/migrations/*.js'], 56 | migrationsRun: true, 57 | }), 58 | }), 59 | ], 60 | }) 61 | ``` 62 | > If you want the migrations not to run automatically then you can set `migrationsRun: false` 63 | 64 | Once this is completed then the setup is complete and the module is ready for execution. 65 | 66 | ## Changes in `package.json` 67 | ``` 68 | scripts :{ 69 | ..., 70 | "typeorm": "ts-node --require tsconfig-paths/register ./node_modules/typeorm/cli.js --config src/db/typeorm.config.ts", 71 | "migration:generate": "npm run typeorm migration:generate -- -n migration" 72 | } 73 | ``` 74 | > If you do not set `--config` parameter typeorm seek a valid configuration file at the root of the project. 75 | 76 | ## Running the module. 77 | 1. Run the following command 78 | ``` 79 | `npm run migration:generate` 80 | ``` 81 | 82 | 2. Check the migration queries which will be formed in the `migrations` folder in the `src/db/migrations` directory. 83 | 84 | 3. If everything went well, you have up to date entites and a `migrations` table listing applied migrations in the database. 85 | 86 | ## References 87 | [TypeORM Migrations](https://github.com/typeorm/typeorm/blob/master/docs/migrations.md) 88 | -------------------------------------------------------------------------------- /wiki/docs/modules/logger.md: -------------------------------------------------------------------------------- 1 | # Logger 2 | 3 | Logger module is already present in Boiler Plate. We have used [winston](https://www.npmjs.com/package/winston) npm package to extend the logger functionality. The objective of the logger module is 4 | 5 | 1. It should support text-based logging 6 | 2. Defining different levels of logs like warn, error, debug etc. 7 | 8 | ## Usability 9 | Logger is part of CoreModule imports which would be default enabled for every application. To call the logger module we need to import it as dependency and then call the desired function for logging info 10 | 11 | 12 | !['database config'](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/images/logger-function.png?raw=true) 13 | 14 | Logger module has different level of functions for logging 15 | 16 | ``` 17 | constructor( private appLogger: AppLogger) {} 18 | 19 | this.appLogger.log('API called'); 20 | ``` 21 | 22 | Logs are created in logs/app.log file at project root level below 23 | ## Output format 24 | ```Log format would be (datetime [app_name] level: message)eg. 2021-06-15T05:45:28.894Z [Boiler plate NestJS] info: API called ``` 25 | 26 | -------------------------------------------------------------------------------- /wiki/docs/modules/mailer.md: -------------------------------------------------------------------------------- 1 | # Mailer 2 | 3 | The objective of Mailer module 4 | 5 | 1) This module would support sending emails via SMTP which stands for Simple Mail Transfer Protocol. We need to provide SMTP credentials in .env in the desired format mentioned below. 6 | 2) Mailer module also supports the attachment functionality. 7 | 3) We need to define the email template folder from where we need fetch the email template. 8 | 4) Currently we support pug email templates 9 | 5) The third-party service that we are using is the nodemailer package. 10 | 11 | # Pug 12 | [Pug](https://www.npmjs.com/package/pug) is a high performance template engine heavily influenced by [Haml](https://haml.info/) and implemented with JavaScript for Node.js and browsers. This library is used for designing the template of the email body. 13 | 14 | ## Install Dependencies 15 | First we need to install the nodemailer package by the following command. 16 | ``` 17 | npm install --save nodemailer 18 | ``` 19 | The below command is executed to support .ts files. 20 | ``` 21 | npm install --save @types/nodemailer 22 | ``` 23 | To install Pug template execute the following instruction. 24 | For .js 25 | ``` 26 | npm install --save pug 27 | ``` 28 | For .ts 29 | ``` 30 | npm install --save @types/pug 31 | ``` 32 | ## Configuration 33 | To configure the SMTP service we need to add below values in the environment. The values are self explanatory. 34 | ``` 35 | EMAIL_HOST= 36 | EMAIL_PORT= 37 | USER_EMAIL= 38 | USER_PASSWORD= 39 | ``` 40 | Once the .env is setup then the values of the .env file will be imported in the application with the help of mailer.ts file present in the config directory. 41 | ``` 42 | export default registerAs('mailer', () => ({ 43 | fromEmail: process.env.USER_EMAIL, 44 | host: process.env.EMAIL_HOST , 45 | port: process.env.EMAIL_PORT , 46 | username: process.env.USER_EMAIL , 47 | password: process.env.USER_PASSWORD , 48 | })); 49 | ``` 50 | 51 | ## Usability 52 | Since Mailer is required in every module it is the part of the CoreModule imports. 53 | Also it is an independent module hence a mailer.module.ts file is being created in which we have provided the EmailHandlerService. 54 | ``` 55 | @Module({ 56 | providers: [EmailHandlerService], 57 | exports: [EmailHandlerService], 58 | }) 59 | export class EmailHandlerModule {} 60 | ``` 61 | The class EmailHandlerModule can be used via importing it in any of the core module such as Users module or App module etc. Consider we want to use the Mailer module in the Users module. It can be done simply by the below steps. First import the ```EmailHandlerModule``` in the user.module.ts file. 62 | ``` 63 | @Module({ 64 | imports: [TypeOrmModule.forFeature([UserDbRepository]), EmailHandlerModule], 65 | providers: [UsersService], 66 | controllers: [UsersController], 67 | }) 68 | export class UsersModule {} 69 | ``` 70 | Now since we need it in the User module we need to add the EmailHandlerService as a dependency in the user.controller.ts file. 71 | 72 | ```constructor(private readonly emailService: EmailHandlerService) {} ``` 73 | 74 | Finally to trigger an email, we need pass below parameters to sendMail function in EmailHandlerService. 75 | 76 | In this case we are sending template as a mail. 77 | ``` 78 | await this.emailService.sendEmail({ 79 | to:'', 80 | subject:'subject', 81 | templateName:'sample', 82 | replace:{'name': ''}, 83 | }); 84 | ``` 85 | The paramenters of the ```sendEmail``` function are self explanatory. The ```templateName``` parameter defines the template od the email that we are going to send through the ```sendEmail``` function. 86 | The value sample is the ```sample.pug``` file which contains the template that is to be sent in the mail. The ```.pug``` should be kept in the template fol 87 | 88 | > template: The ```.pug``` file should be kept in the template folder whose directory might be in the following format /src/core/mailer/template/sample.pug 89 | 90 | At the same time if we want to send a textbody via email then it has to be defined 91 | in as a value against the ```body``` parameter. Use the following snippet of the code. 92 | ``` 93 | await this.emailService.sendEmail({ 94 | to:'', 95 | subject:'subject', 96 | body:'This is email body', 97 | }); 98 | ``` 99 | -------------------------------------------------------------------------------- /wiki/docs/modules/pattern.md: -------------------------------------------------------------------------------- 1 | Design Pattern 2 | 3 | ## Clean architecture 4 | 5 | > Separation of concerns at the architectural level 6 | 7 | This document defines the design pattern for developing any web api application. We intend to follow clean architecture. Dividing the application into multiple layers like Domain layer, Service layer, Repository layer etc. The implementation of these layer would explained via example. For more understanding kindly refer Clean Architecture related articles. 8 | 9 | In this boiler plate, we define the methods and techniques that would useful for building and maintaining web app build in NestJS. NestJS itself has covered alot techniques like TypeORM, Dependency Injection and so on. This document help us to utilize functionality and follow clean architecture. 10 | 11 | !['Code Flow'](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/images/code-flow.jpg?raw=true) 12 | 13 | In the above diagram, we code the see the how does the request flows from controller to database. There are lot of aspects that need to consider in this, like external api calls, caching etc which would be covered later. 14 | 15 | ### Inversion of Control (IoC) 16 | 17 | In software engineering, inversion of control (IoC) is a programming principle. IoC inverts the flow of control as compared to traditional control flow. In IoC, custom-written portions of a computer program receive the flow of control from a generic framework. A software architecture with this design inverts control as compared to traditional procedural programming: in traditional programming, the custom code that expresses the purpose of the program calls into reusable libraries to take care of generic tasks, but with inversion of control, it is the framework that calls into the custom, or task-specific, code. 18 | 19 | Below is the inversion of flow for the boiler plate which has been used for user module. 20 | 21 | !['Inversion of control](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/Inversion-of-control.jpg?raw=true) 22 | 23 | The control flows from UserController to UserService, we have DTO defined for transfering data between classes eg. create.user.dto, update.user.dto 24 | 25 | ## Understanding the implementation of clean code in boilerplate 26 | 27 | To start with let's understand the folder structure and files first. 28 | 29 | !['Folder structure](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/folder-structure.png?raw=true) 30 | 31 | App.module — the core of the project, here we import modules that are providing functionalities. 32 | users.module — this module will be imported by AppModule to provide everything that is related to our users. 33 | users.controller — Here we declare our endpoints with all parameters. 34 | entities — in this place we declare our User class. 35 | dto— data transfer object — a bunch of interfaces for communication with the application. 36 | repository — in this place we define all the queries related to database or external IO. 37 | services - in this place we have our business logic defined 38 | 39 | Now let us go through each folder 40 | 41 | ## 1. entities 42 | 43 | In this folder, we have defined the database table fields that is required for that entity. TypeORM has some build-in helpers like PrimaryGeneratedColumn, Column etc. Ref: [TypeORM documentation](https://typeorm.io/#/entities) 44 | 45 | ## 2. dto 46 | 47 | In this folder, we have defined the the data transfer object that would be used to communicated between classes. DTO are helpful for avoid leak of unwanted data or fields 48 | 49 | ## 3. repository 50 | 51 | In this folder, we have defined the database queries related to the entity. We have used typeORM for fetching or saving data. Before defining the implementation of the repository, it is a good practice to define an interface of the repository. 52 | 53 | ## 4. services 54 | 55 | In this folder, we have defined the business logic related to the entity. For executing the db queries, we use the repository. Repository and services are loosely coupled with the repository interface. 56 | -------------------------------------------------------------------------------- /wiki/docs/modules/request-response.md: -------------------------------------------------------------------------------- 1 | # Request Response 2 | Boilerplate has a custom guard enabled for handling response and request for every api. The integration of request response guard is enabled by default with response structure 3 | 4 | ## Overview 5 | 6 | !['Request Response Life Cycle'](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/images/nestjs-request-life-cycle.png?raw=true) 7 | 8 | ## Available function for response. 9 | 10 | ### **success** 11 | 12 | !['success response'](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/images/success-response.png?raw=true) 13 | 14 | The success function expected two parameters 15 | 1) data: Json response data. 16 | 2) Status code: API response status code, we have used [http-status-codes](https://www.npmjs.com/package/http-status-codes) enums for different type of response code. 17 | 18 | ### **error** 19 | 20 | !['error response'](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/images/error-response.png?raw=true) 21 | 22 | The error function expected two parameters 23 | 1) error: Json response data. 24 | 2) Status code: API response status code, we have used [http-status-codes](https://www.npmjs.com/package/http-status-codes) enums for different type of response code. 25 | 26 | ### **noContent** 27 | 28 | !['noContent response'](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/images/no-content-response.png?raw=true) 29 | 30 | The noContent function expected no parameters 31 | 32 | 33 | ### **withMeta** 34 | 35 | !['meta response'](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/images/meta-response.png?raw=true) 36 | 37 | The withMeta function expected two parameters. This function extract additional data passed to response object into new response key as meta. 38 | 1) data: Json response data. 39 | 2) Status code: API response status code, we have used [http-status-codes](https://www.npmjs.com/package/http-status-codes) enums for different type of response code. 40 | 41 | ## Usability: 42 | 43 | We need to import the Request and Response interface from the core modules 44 | 45 | ```import { Request, Response } from '@libs/core';``` 46 | ``` 47 | @Controller() 48 | export class AppController { 49 | constructor(private readonly appService: AppService, private appLogger: AppLogger) {} 50 | 51 | @Get() 52 | getHello(@Req() req: Request, @Res() res: Response): Promise { 53 | this.appLogger.log('API called'); 54 | return res.success(this.appService.getHello()); 55 | } 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /wiki/docs/modules/reverse_proxy.md: -------------------------------------------------------------------------------- 1 | # Reverse Proxy Support 2 | ## Introduction 3 | - A reverse proxy is an intermediate connection point positioned at a network's edge. 4 | - A **reverse proxy server** is a type of proxy server that typically sits behind the firewall in a private network and directs client requests to the appropriate backend server. 5 | - Reverse proxy support has its many advantages some of them are listed below. 6 | 1. Load Balancing. 7 | 2. Web acceleration. 8 | 3. Security and anonymity. 9 | 10 | ## Why Reverse Proxy Support? 11 | - The primary reason for Reverse Proxy Support in the application is to make the application available thorugh custom domain name. 12 | - We will be using the **Nginx Service** in order to implement Reverse Proxy Support for our application. 13 | - We have used the **Nginx Service** because most importantly it is and open source, it can handle a karge volume of connection and is widely used around the world by many prominent tech companies. 14 | 15 | ## Configuration 16 | 1. For setting up the Nginx Server we need to first need to create a docker image of Nginx which is to be done in the **docker-compose.yml** file which is present in the **docker** directory inside the **root** directory. 17 | The working directory of the docker-compose can be understood by the following image. 18 | ``` 19 | nginx: 20 | image: nginx:1.20 21 | container_name: '${APP_NAME}_nginx' 22 | networks: 23 | - internal 24 | volumes: 25 | - ./nginx.conf:/etc/nginx/conf.d/default.conf 26 | - ./certs:/etc/nginx/certs 27 | ports: 28 | - '80:80' 29 | - '443:443' 30 | healthcheck: 31 | test: ["CMD", "wget", "-qO-", "http://localhost:5000"] 32 | interval: 2s 33 | timeout: 60s 34 | retries: 30 35 | ``` 36 | 2. The nginx image version we are using is 1.20 since it is the current stable version of **Nginx service** 37 | 3. In the volumes section 38 | > ./nginx.conf:/etc/nginx/conf.d/default.conf this represents that the nginx.conf file **overrides** the default.conf file in /etc/nginx/conf.d/default.conf. 39 | 4. Meaning of **80:80** means the 80 port of Host maching has been mapped to the port 80 of the docker. 40 | 5. Port **80:80** has been used since it is the default port of Nginx Server and **443:443** port is being used for converting the application from **http to https** protocol. 41 | 6. The **healthcheck** part of the nginx docker-compose is used to check whether the nginx have found the host successfully or not. 42 | 7. Once this setup is done then we move on the **nginx.conf** file for the setup of http server. 43 | 8. The setup is done as follows. 44 | ``` 45 | server { 46 | listen 80; 47 | server_name localhost; 48 | location / { 49 | root /usr/share/nginx/html; 50 | index index.html index.htm; 51 | } 52 | } 53 | server { 54 | listen 80; 55 | server_name testurl.neosoft.com; 56 | location / { 57 | proxy_pass http://rest_api_nestjs:5000; 58 | } 59 | } 60 | ``` 61 | - In the above piece of code we are bypassing our localhost which is represented as **http://rest_api_nestjs:5000**. 62 | - Also we need to make a small change in **package.json** file which is as follows. 63 | ``` 64 | "db:setup": "docker-compose -f ./docker/docker-compose.yml --env-file ./config/env/.env up -d --build && docker start rest_api_nginx", 65 | 66 | ``` 67 | - Once everything is done then run the command **npm run db:setup** and open any browser and type the url **testurl.neosoft.com** we get the following output. 68 | 69 | 70 | ## Dockerizing NestJs application. 71 | 1. In this part we need to dockerize our nestJs application which will help us to save space of our local machine and help us for easy deployment in the cloud. 72 | 2. We need to follow the below steps in order to dockerize our NestJs app. 73 | 74 | ## Steps 75 | - Create a DockerFile in thr **rootDir** 76 | - Add the followind code in the Dockerfile 77 | ``` 78 | FROM node:12 79 | WORKDIR /usr/src/app 80 | COPY package*.json ./ 81 | RUN npm install 82 | COPY . . 83 | RUN npm run build 84 | EXPOSE 5000 85 | CMD [ "npm", "run", "start" ] 86 | ``` 87 | - Then we need to create the service of nestjs app inside the docker-compose.yml file which is as follows. 88 | ``` 89 | nestjs: 90 | build: 91 | context: ./../ 92 | container_name: '${APP_NAME}_nestjs' 93 | volumes: 94 | - ./../:/usr/src/app 95 | networks: 96 | - internal 97 | depends_on: 98 | - database 99 | ports: 100 | - '5000:5000' 101 | ``` 102 | - In this we are exposing our application to the 5000 port 103 | > **Note** It is very important that the nest js application is dependent on the database service hence we are adding the **depends_on** service. -------------------------------------------------------------------------------- /wiki/docs/modules/ssl.md: -------------------------------------------------------------------------------- 1 | # SSL Configuration 2 | 3 | An `SSL` certificate is a digital certificate that authenticates a website's identity and enables an encrypted connection. `SSL` stands for `Secure Sockets Layer`, a security protocol that creates an encrypted link between a web server and a web browser. 4 | ##### The Objective of SSL configuration is as follows: 5 | - The primary reason why SSL is used is to keep sensitive information sent across the Internet encrypted so that only the intended recipient can access it. 6 | - In addition to encryption, a proper SSL certificate also provides authentication. 7 | - `SSL` is required for `PCI(Payment Card Industry)` compliance. 8 | 9 | # Configuration 10 | 1. We will be generating the self signed certificate using [OpenSSL](https://help.ubuntu.com/community/OpenSSL) 11 | 2. First we have to check whether we have SSL installed in our system or not. If using Ubuntu then type the following below command in the terminal. 12 | ```bash 13 | $ openssl 14 | ``` 15 | >The terminal should show the below output: 16 | 17 | ![SSL-Terminal](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/dev/wiki/images/terminal-ssl.png?raw=true) 18 | 19 | 3. If the terminal shows `command not found` then click on the link below for the installation. 20 | [OpenSSL Installation](https://linuxtect.com/how-to-install-openssl-libraries-on-ubuntu-debian-mint/) 21 | 22 | ## Generating private SSL key 23 | Step 1: First create a folder in src folder in the root directory and name it as cert. 24 | > Path: /src/cert 25 | 26 | To generate the private key we have to run the followig command inside the terminal in linux and command prompt in Windows. 27 | ``` 28 | openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 29 | ``` 30 | Lets understand the parameters of the above command. 31 | * **`req`**` : The req command primarily creates and processes certificate requests in PKCS#10 format. 32 | 33 | * **`-x509`** : Creates a X.509 Certificate. 34 | 35 | * **`-newkey rsa:4096`** : Creates a new certificate request and 4096 bit RSA key. The default one is 2048 bits. 36 | 37 | * **`-keyout key.pem`** : Specifies the filename to write the newly created private key to. You can specify any file name. 38 | 39 | * **`-out`** : Specifies the filename to write the newly created certificate to. You can specify any file name. 40 | 41 | * **`-days 365`** :The number of days to certify the certificate for. 3650 is ten years. You can use any positive integer. 42 | 43 | Once this command is executed in the terminal then we get the following output. 44 | ![SSL-Output](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/dev/wiki/images/ssl-command.png?raw=true). 45 | It will ask us `PEM` pass phrase. This pass phrase should be a secret key that should be any string in an encrypted format. This pass phrase is mandatory for generation of certificate and the key. After the `pem` pass phrase is entered then the following screen should be shown. 46 | 47 | ![PEM-PASS](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/dev/wiki/images/pem-pass.png?raw=true) 48 | 49 | >Once the above screen is show then check the directory : /src/cert, it will contain two files which represnts the key and the certificate. 50 | 51 | ## Usability 52 | 1. Since the SSL certificate is used for the whole NestJS application we have to include it into the ```bootstrap``` function in the main.ts file. 53 | 2. Once it is included there then it is available for the whole application. 54 | 55 | >The output of the following is show below. 56 | 57 | ![Https-Output](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/https-support/wiki/images/https-output.PNG?raw=true) 58 | -------------------------------------------------------------------------------- /wiki/docs/modules/swagger.md: -------------------------------------------------------------------------------- 1 | ## Swagger/Open API 2 | 3 | - The [OpenAPI](https://swagger.io/specification/) specification is a language-agnostic definition format used to describe RESTful APIs. Nest provides a dedicated [module](https://github.com/nestjs/swagger) which allows generating such a specification by leveraging decorators. 4 | 5 | ### Refrences 6 | 7 | - [OpenAPI (Swagger) | NestJS](https://docs.nestjs.com/openapi/introduction) 8 | 9 | ### Installation 10 | 11 | To begin using it, we first install the required dependencies. 12 | 13 | `npm install --save-dev @nestjs/swagger swagger-ui-express` 14 | 15 | ### Implemenetation 16 | 17 | To begin, we will create a new file called `src/swagger/index.ts` into which we will add the following code. 18 | 19 | ``` 20 | import { INestApplication } from '@nestjs/common'; 21 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 22 | import { ConfigService } from '@nestjs/config'; 23 | 24 | export const setupSwagger = (app: INestApplication) => { 25 | const config = app.get(ConfigService); 26 | const options = new DocumentBuilder() 27 | .setTitle(config.get(config.get('app.name'))) 28 | .setDescription(`API Documentation for the app ${config.get('app.name')}`) 29 | .setVersion(config.get('app.vesrion')) 30 | .addBearerAuth() 31 | .build(); 32 | const document = SwaggerModule.createDocument(app, options); 33 | SwaggerModule.setup('api/docs', app, document); 34 | }; 35 | ``` 36 | 37 | We need to add the following code to the `src/main.ts` file to call this code, If the environment is dev/local/staging, then only swagger will be enabled. 38 | 39 | ``` 40 | const envList = ['dev', 'staging', 'local', 'test']; 41 | 42 | if (envList.includes(config.get('app.env'))) { 43 | setupSwagger(app); 44 | } 45 | ``` 46 | 47 | ### Run the Swagger 48 | 49 | You can check [swagger](http://localhost:5000/api/docs) running on [`http://localhost:5000/api/docs`](http://localhost:5000/api/docs) -------------------------------------------------------------------------------- /wiki/docs/summary.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Contribution", 4 | "file": "contribution.md", 5 | "children": [ 6 | { 7 | "title": "Bug Reports", 8 | "file": "contribution/bug-reports.md" 9 | }, 10 | { 11 | "title": "Features Requests", 12 | "file": "contribution/feature-requests.md" 13 | }, 14 | { 15 | "title": "Pull Requests", 16 | "file": "contribution/pull-requests.md" 17 | } 18 | ] 19 | }, 20 | { 21 | "title": "Modules", 22 | "file": "modules.md", 23 | "children": [ 24 | { 25 | "title": "Logger", 26 | "file": "modules/logger.md" 27 | }, 28 | { 29 | "title": "Request Response", 30 | "file": "modules/request-response.md" 31 | }, 32 | { 33 | "title": "Mailer", 34 | "file": "modules/mailer.md" 35 | }, 36 | { 37 | "title": "Database", 38 | "file": "modules/database.md" 39 | }, 40 | { 41 | "title": "Pattern", 42 | "file": "modules/pattern.md" 43 | }, 44 | { 45 | "title": "Crypto", 46 | "file": "modules/crypto.md" 47 | }, 48 | { 49 | "title": "Open API/Swagger", 50 | "file": "modules/swagger.md" 51 | } 52 | ] 53 | }, 54 | { 55 | "title": "Miscellaneous", 56 | "file": "miscellaneous.md", 57 | "children": [ 58 | { 59 | "title": "Git commits", 60 | "file": "miscellaneous/git-commits.md" 61 | }, 62 | { 63 | "title": "Known Issues", 64 | "file": "miscellaneous/known-issues.md" 65 | }, 66 | { 67 | "title": "Clean Docker Images", 68 | "file": "miscellaneous/clean-docker.md" 69 | }, 70 | { 71 | "title": "Installation Pretteri, Husky & Lint", 72 | "file": "miscellaneous/installation-pretteri-husky-lint.md" 73 | } 74 | ] 75 | }, 76 | { 77 | "title": "Trainings", 78 | "file": "trainings.md", 79 | "children": [ 80 | { 81 | "title": "Node JS for Beginners", 82 | "file": "trainings/nodejs.md" 83 | }, 84 | { 85 | "title": "Nest JS for Beginners", 86 | "file": "trainings/nestjs.md" 87 | }, 88 | { 89 | "title": "Git for Beginners", 90 | "file": "trainings/git.md" 91 | } 92 | ] 93 | } 94 | ] 95 | -------------------------------------------------------------------------------- /wiki/docs/trainings.md: -------------------------------------------------------------------------------- 1 | # Trainings 2 | 3 | - [Git for Beginners](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/trainings/git.md) 4 | - [Node JS for Beginners](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/trainings/nodejs.md) 5 | - [Nest JS for Beginners](https://github.com/NeoSOFT-Technologies/rest-node-nestjs/blob/main/wiki/docs/trainings/nestjs.md) 6 | -------------------------------------------------------------------------------- /wiki/docs/trainings/git.md: -------------------------------------------------------------------------------- 1 | # Git 2 | 3 | [Git Cheat Sheet – 50 Git Commands You Should Know](https://www.freecodecamp.org/news/git-cheat-sheet/) 4 | [Git Cheat Sheet](https://training.github.com/downloads/github-git-cheat-sheet.pdf) -------------------------------------------------------------------------------- /wiki/docs/trainings/nestjs.md: -------------------------------------------------------------------------------- 1 | # Nest JS Training 2 | 3 | ## Prerequisites 4 | - Node JS 5 | - Express JS 6 | - Typescript 7 | 8 | ## Topics 9 | 10 | ### 1. Getting Started 11 | - Introduction to Nest JS 12 | - Rest API 13 | - Microservices 14 | - Web Sockets 15 | - Graphql 16 | - Installing the NestJS CLI (command-line interface) 17 | - Installing Node.js 18 | - Install NestJS CLI -globally 19 | - Generating Nest JS Application 20 | - What’s inside a Nest JS Application 21 | 22 | ### 2. Creating a REST API application 23 | - Prerequisite: Install Insomnia/Postman 24 | - Creating a Basic Controller 25 | - nest generate controller 26 | - Use Route Parameters 27 | - Handling Request Body / Payload 28 | - Response Status Codes 29 | - Handling Update and Delete Requests 30 | - Implement Pagination with Query Parameters 31 | - Creating a Basic Service 32 | - nest generate service {name} 33 | - Send User-Friendly Error Messages 34 | - Encompass Business-Domain in Modules 35 | - controllers - Which you can think of as our API Routes, that we want this module to instantiate. 36 | - exports - Here we can list providers within this current module that should be made available anywhere this module is imported 37 | - imports - Just as we saw in the AppModule, the imports Array gives us the ability to list OTHER modules that THIS module requires. Any exported providers of these imported modules are now fully available here as well. 38 | - providers - Here we’re going to list our services that need to be instantiated by the Nest injector. Any providers here will be available only within “THIS” module itself, unless added to the exports array we saw above. 39 | - Introduction to Data Transfer Objects 40 | - Generate a DTO class with the Nest CLI 41 | - nest g class coffees/dto/create-coffee.dto --no-spec 42 | - Validate Input Data with Data Transfer Objects 43 | - ValidationPipe 44 | - Install needed dependencies - npm i class-validator class-transformer 45 | - Handling Malicious Request Data 46 | - Auto-transform Payloads to DTO instances 47 | 48 | ### 3. Add PostgreSQL with Type ORM 49 | - Prerequisite: Install Docker 50 | - Running PostgreSQL 51 | - How to visualize your Postgres Database (GUI) 52 | - https://www.pgadmin.org/ 53 | - Introducing the Type ORM Module 54 | - Creating a Type ORM Entity 55 | - Using Repository to Access Database 56 | - Create a Relation between two Entities 57 | - Relations 58 | - One-to-one 59 | - One-to-many or Many-to-one relations 60 | - Many-to-many relations 61 | - Retrieve Entities with their Relations 62 | - Using Cascading Inserts and Updates 63 | - Adding Pagination 64 | - Use Transactions 65 | - Adding Indexes to Entities 66 | - Setting up Migrations 67 | 68 | ### 4. Dependency Injection 69 | - Understand Dependency Injection 70 | - Injector 71 | - Container 72 | - Controller 73 | - Service 74 | - Control Nest JS Module Encapsulation 75 | - Diving Into Custom Providers 76 | - Strategy Pattern 77 | - Value based Providers 78 | - Non-class-based Provider Tokens 79 | - Class Providers 80 | - Factory Providers 81 | - Leverage Async Providers 82 | - Create a Dynamic Module 83 | - Control Providers Scope 84 | - Diving Deeper Into Request-Scoped Providers 85 | 86 | ### 5. Application Configuration 87 | 88 | - Introducing the Config Module 89 | - Custom Environment File Paths 90 | - Schema Validation 91 | - Using the Config Service 92 | - Custom Configuration Files 93 | - Configuration Namespaces and Partial Registration 94 | - Asynchronously Configure Dynamic Modules 95 | 96 | ### 6. Other Building Blocks by Example 97 | - Introducing More Building Blocks 98 | - Understanding Binding Techniques 99 | - Catch Exceptions with Filters 100 | - Protect Routes with Guards 101 | - Using Metadata to Build Generic Guards or Interceptors 102 | - Add Pointcuts with Interceptors 103 | - Handling Timeouts with Interceptors 104 | - Creating Custom Pipes 105 | - Add Request Logging with Middleware 106 | - Create Custom Param Decorators 107 | 108 | ### 7. Generating Open API Specification 109 | - Introducing the Swagger Module 110 | - Enabling CLI Plugin 111 | - Decorating Model Properties 112 | - Adding Example Responses 113 | - Using Tags to Group Resources 114 | 115 | ### 8. Testing 116 | - Introduction to Jest 117 | - Getting Started with Test Suites 118 | - Adding Unit Tests 119 | - Diving Into e2e Tests 120 | - Creating our First e2e Test 121 | - Implementing e2e Test Logic 122 | 123 | ### 9. Add MongoDB with Mongoose 124 | - Running MongoDB 125 | - Introducing the Mongoose Module 126 | - Creating a Mongoose Model 127 | - Using a Mongoose Model to Access MongoDB 128 | - Adding Pagination 129 | - Use Transactions 130 | - Adding Indexes to Schemas 131 | 132 | ## References 133 | 134 | - [Who is using Nest in production?](https://github.com/nestjs/nest/issues/1006) 135 | - [https://learn.nestjs.com/p/fundamentals](https://learn.nestjs.com/p/fundamentals ) 136 | - [https://github.com/juliandavidmr/awesome-nestjs](https://github.com/juliandavidmr/awesome-nestjs) -------------------------------------------------------------------------------- /wiki/docs/trainings/nodejs.md: -------------------------------------------------------------------------------- 1 | # NodeJS Training 2 | ## Prerequisites 3 | - JavaScript 4 | - Typescript 5 | - npm 6 | 7 | ## Topics 8 | 9 | ### 1. Introduction & Foundation 10 | - Introduction 11 | - The Node.js framework 12 | - Explain what Node.js is 13 | - Describe how Node.js works 14 | - Identify when to use Node.js 15 | - Create and run a Node.js script from the command line 16 | - Event Loop 17 | - What is the Event Loop? 18 | - Event Loop Explained 19 | - Phases Overview 20 | - Why use process.nextTick()? 21 | - Single threaded Architecture 22 | - V8 JavaScript Engine 23 | - C library - libuv 24 | - Process & Thread 25 | - Thread pool 26 | - Understand Node JS Single Thread Event Loop Workflow 27 | 28 | ### 2. Node Projects 29 | - Node Project Manager 30 | - Initialize Node.js projects 31 | - npm and nvm 32 | - package.json configuration file 33 | - Global vs local package installation 34 | 35 | ### 3. Starting with Node js 36 | - Global Objects (http://nodejs.org/api/globals.html) 37 | - __filename 38 | - __dirname 39 | - module 40 | - exports 41 | - process 42 | - Buffer 43 | - Console (http://nodejs.org/api/console.html) 44 | - log 45 | - info 46 | - error 47 | - warn 48 | - dir 49 | - time 50 | - timeEnd 51 | - trace 52 | - assert 53 | - Timers (http://nodejs.org/api/timers.html) 54 | - setTimeout(callback, delay, [arg], [...]) 55 | - clearTimeout(t) 56 | - setInterval(callback, delay, [arg], [...]) 57 | - clearInterval(t) 58 | - setImmediate(callback, [arg], [...]) 59 | - clearImmediate(immediateObject) 60 | - unref() 61 | - ref() 62 | - Modules (http://nodejs.org/api/modules.html) 63 | - Debugging(https://nodejs.org/en/docs/guides/debugging-getting-started) 64 | - --inspect 65 | - Security Implications 66 | - node-inspector(https://github.com/node-inspector/node-inspector) 67 | 68 | ### 4. Express JS 69 | - Model View Controller Pattern 70 | - Template Engine 71 | - Using REST 72 | - Middleware 73 | 74 | ### 5. Unit Testing 75 | - Basic Introduction 76 | - Assert (http://nodejs.org/api/assert.html) 77 | - Functional and Integration testing 78 | - Implementation of unit testing using Mocha or Chai 79 | 80 | ### 6. Express JS Practical 81 | - Setup the new Project 82 | - Add the unit test cases 83 | 84 | ### 7. Working with Asynchronous Programming 85 | - Asynchronous basics 86 | - Callback Functions 87 | - Working with Promises 88 | - API Calls 89 | 90 | ### 8. Building a HTTP Server with Node JS using HTTP APIs 91 | - HTTP protocol (http://nodejs.org/api/http.html) 92 | - To use the HTTP server and client one must require('http'). 93 | - HTTP server & HTTPS Server 94 | - URL (http://nodejs.org/api/url.htm) 95 | - This module has utilities for URL resolution and parsing. Call require('url') to use it. 96 | - Query String (http://nodejs.org/api/querystring.html) 97 | - This module provides utilities for dealing with query strings. Call require('querystring') to use it 98 | 99 | ### 9. File System 100 | - Basic (http://nodejs.org/api/fs.html) 101 | - To use this module do require('fs'). 102 | - All the methods have asynchronous and synchronous forms. 103 | - Synchronous vs Asynchronous IO 104 | - File Read and Write 105 | - Util (http://nodejs.org/api/util.html) 106 | - Path 107 | - Use require('path') to use this module. 108 | - This module contains utilities for handling and transforming file paths. 109 | - Almost all these methods perform only string transformations. 110 | 111 | ### 10. Buffers, Streams, and Events 112 | - Buffers for binary Data (http://nodejs.org/api/buffer.html) 113 | - Buffer is used to dealing with binary data 114 | - Buffer is similar to an array of integers but corresponds to a raw memory allocation outside the V8 heap 115 | - Event Emitter(http://nodejs.org/api/events.html) 116 | - addListener 117 | - on 118 | - once 119 | - removeListener 120 | - removeAllListeners 121 | - setMaxListeners 122 | - listeners 123 | - emit 124 | - Stream (http://nodejs.org/api/stream.html) 125 | - Flowing vs non-flowing streams 126 | - Streams are readable, writable, or both. 127 | - readable streams 128 | - http responses on the client 129 | - http requests on the server 130 | - fs read streams 131 | - zlib streams (https://nodejs.org/api/zlib.html) 132 | - crypto streams (https://nodejs.org/api/crypto.html) 133 | - tcp sockets 134 | - child process stdout and stderr 135 | - process.stdin 136 | - writable streams 137 | - http requests on the client 138 | - http responses on the server 139 | - fs write streams 140 | - zlib streams 141 | - crypto streams 142 | - tcp sockets 143 | - child process stdin 144 | - process.stdout 145 | - process.stderr 146 | - Duplex streams 147 | - tcp sockets 148 | - zlib streams 149 | - crypto streams 150 | - Transform streams 151 | - zlib streams 152 | - crypto streams 153 | - File System and Security (Example - Build one system) 154 | - API to upload file 155 | - Wrapper to upload file 156 | - Web-hooks 157 | 158 | ### 11. Multi-Processing in NodeJS 159 | - Process(http://nodejs.org/api/process.html) 160 | - Child Process (http://nodejs.org/api/child_process.html) 161 | - Node provides a tri-directional popen facility through the child_process module. 162 | - It is possible to stream data through a child's stdin, stdout, and stderr in a fully non-blocking way. 163 | - Cluster API for multi-core servers 164 | - Multi-Processing 165 | - Provides a few basic operating-system related utility functions 166 | - Use require('os') to access this module. (http://nodejs.org/api/os.html) 167 | 168 | ### 12. Socket.io, The Front-End & A Chat App 169 | - Basic of Socket IO 170 | - Example & Implementation of events, listener, broadcast & emitters 171 | 172 | ### 13. Putting It All Together 173 | - Problem Statement - Event Management System (NodeJS + Postgres/Mongo DB/MySQL) 174 | - User 175 | - Register (User can register) 176 | - Login (User login) 177 | - Logout (User logout) 178 | - Change Password (User can change his password) 179 | - Update Password (When request for reset password is done, to set new password) 180 | - Reset Password (In API response send info regarding to update-password) 181 | - Event (Authentication required for doing operations on event) 182 | - Create (User can create Event) 183 | - Invite (Users) // Just store emails and when that user login in he can see his created event list and also events in which he is invited 184 | - List (Invited users when login can see their events as well events events in which they are invited in, also display creator name in list) 185 | - Sorting 186 | - Date Filter 187 | - Search Filter 188 | - Event Detail + list of users invited (API to get specific event and invites) 189 | - Event update (Event Update) 190 | - Notes 191 | - Consider models as per your knowledge 192 | - Create Postman for above APIs 193 | - Add the unit test cases for APIs 194 | - Skills covered in Node.js tests 195 | - Knowledge of JavaScript 196 | - Asynchronous programming 197 | - Managing databases from Node.js (e.g. MongoDB) 198 | - Processing data structures 199 | - Functional programming with JavaScript 200 | - Object-Oriented programming with JavaScript 201 | - Unit Test cases 202 | 203 | -------------------------------------------------------------------------------- /wiki/images/CORS-console-result.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/CORS-console-result.PNG -------------------------------------------------------------------------------- /wiki/images/CORS-response-headers.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/CORS-response-headers.PNG -------------------------------------------------------------------------------- /wiki/images/Inversion-of-control.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/Inversion-of-control.jpg -------------------------------------------------------------------------------- /wiki/images/basic-nestJS-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/basic-nestJS-lifecycle.png -------------------------------------------------------------------------------- /wiki/images/code-flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/code-flow.jpg -------------------------------------------------------------------------------- /wiki/images/compressed.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/compressed.PNG -------------------------------------------------------------------------------- /wiki/images/database-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/database-config.png -------------------------------------------------------------------------------- /wiki/images/error-response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/error-response.png -------------------------------------------------------------------------------- /wiki/images/folder-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/folder-structure.png -------------------------------------------------------------------------------- /wiki/images/logger-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/logger-function.png -------------------------------------------------------------------------------- /wiki/images/meta-response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/meta-response.png -------------------------------------------------------------------------------- /wiki/images/nestjs-request-life-cycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/nestjs-request-life-cycle.png -------------------------------------------------------------------------------- /wiki/images/no-content-response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/no-content-response.png -------------------------------------------------------------------------------- /wiki/images/nosql-query-injection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/nosql-query-injection.png -------------------------------------------------------------------------------- /wiki/images/pem-pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/pem-pass.png -------------------------------------------------------------------------------- /wiki/images/rate-limit-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/rate-limit-output.png -------------------------------------------------------------------------------- /wiki/images/ssl-command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/ssl-command.png -------------------------------------------------------------------------------- /wiki/images/success-response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/success-response.png -------------------------------------------------------------------------------- /wiki/images/terminal-ssl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/terminal-ssl.png -------------------------------------------------------------------------------- /wiki/images/uncompressed.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/images/uncompressed.PNG -------------------------------------------------------------------------------- /wiki/roadmaps/nestjs-roadmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeoSOFT-Technologies/rest-node-nestjs/1ec5af9f9febf82a200bb3fe5118d0b09872412b/wiki/roadmaps/nestjs-roadmap.png --------------------------------------------------------------------------------