├── .github ├── ISSUE_TEMPLATE │ ├── Bug_report.yml │ ├── Feature_request.yml │ ├── Regression.yml │ └── Suggestion_improve_performance.yml └── PULL_REQUEST_TEMPLATE.md ├── 01-mongoose ├── .prettierrc ├── README.md ├── docker-compose.yml ├── dockerfile ├── env.dev ├── mongoose.postman_collection.json ├── nest-cli.json ├── package-lock.json ├── package.json ├── src │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── app │ │ ├── auth │ │ │ ├── auth.controller.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto │ │ │ │ ├── index.ts │ │ │ │ ├── login.dto.ts │ │ │ │ └── register.dto.ts │ │ │ ├── enum │ │ │ │ ├── index.ts │ │ │ │ ├── response.enum.ts │ │ │ │ └── token.enum.ts │ │ │ ├── guard │ │ │ │ ├── index.ts │ │ │ │ ├── jwt.guard.ts │ │ │ │ ├── jwt.strategy.ts │ │ │ │ ├── refresh-token.guard.ts │ │ │ │ ├── refresh-token.strategy.ts │ │ │ │ └── roles.guard.ts │ │ │ └── interface │ │ │ │ ├── index.ts │ │ │ │ ├── jwt-payload.interface.ts │ │ │ │ └── token-sign-in.interface.ts │ │ ├── common │ │ │ ├── decorator │ │ │ │ ├── array-of-object.decorator.ts │ │ │ │ ├── get-user.decorator.ts │ │ │ │ ├── index.ts │ │ │ │ ├── response.decorator.ts │ │ │ │ └── roles.decorator.ts │ │ │ ├── enum │ │ │ │ ├── connections.enum.ts │ │ │ │ ├── index.ts │ │ │ │ └── roles.enum.ts │ │ │ ├── filter │ │ │ │ ├── exception.filter.ts │ │ │ │ └── index.ts │ │ │ ├── helper │ │ │ │ └── utilities.helper.ts │ │ │ ├── interceptors │ │ │ │ ├── index.ts │ │ │ │ ├── logging.interceptor.ts │ │ │ │ └── response.interceptor.ts │ │ │ └── type │ │ │ │ ├── index.ts │ │ │ │ ├── mixed-array.type.ts │ │ │ │ ├── sales-unit-calculation.type.ts │ │ │ │ └── send-sms.type.ts │ │ └── modules │ │ │ ├── index.ts │ │ │ ├── todo │ │ │ ├── dto │ │ │ │ ├── add-todo.dto.ts │ │ │ │ ├── delete-todo.dto.ts │ │ │ │ └── index.ts │ │ │ ├── enum │ │ │ │ ├── index.ts │ │ │ │ └── response-enum.ts │ │ │ ├── schema │ │ │ │ ├── index.ts │ │ │ │ └── todo.schema.ts │ │ │ ├── todo.controller.ts │ │ │ ├── todo.module.ts │ │ │ └── todo.service.ts │ │ │ └── user │ │ │ ├── dto │ │ │ ├── delete-user.dto.ts │ │ │ ├── index.ts │ │ │ └── update-user.dto.ts │ │ │ ├── enum │ │ │ ├── index.ts │ │ │ └── response-enum.ts │ │ │ ├── schema │ │ │ ├── index.ts │ │ │ └── user.schema.ts │ │ │ ├── user.controller.ts │ │ │ ├── user.module.ts │ │ │ └── user.service.ts │ ├── config │ │ ├── config.default.ts │ │ ├── config.interface.ts │ │ ├── config.module.ts │ │ └── config.service.ts │ ├── database │ │ ├── database.module.ts │ │ └── db.error.ts │ ├── logger │ │ ├── logger.middleware.ts │ │ ├── logger.module.ts │ │ ├── logger.spec.ts │ │ ├── logger.ts │ │ └── loglevel.ts │ ├── main.ts │ └── swagger │ │ ├── swagger.config.ts │ │ ├── swagger.interface.ts │ │ └── swagger.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md └── README.md /.github/ISSUE_TEMPLATE/Bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41B Bug Report" 2 | description: "If something isn't working as expected \U0001F914" 3 | labels: ["needs triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | ## :warning: We use GitHub Issues to track bug reports, feature requests and regressions 9 | 10 | If you are not sure that your issue is a bug, you could: 11 | 12 | - use Official[Discord community](https://discord.gg/NestJS) 13 | - use [StackOverflow using the tag `nestjs`](https://stackoverflow.com/questions/tagged/nestjs) 14 | - If it's just a quick question you can ping me on my [Twitter](https://twitter.com/CodeAshing) 15 | 16 | **NOTE:** You don't need to answer questions that you know that aren't relevant. 17 | 18 | --- 19 | 20 | - type: dropdown 21 | validations: 22 | required: true 23 | attributes: 24 | label: "Project Name" 25 | description: "Select the project you intend to improve" 26 | options: 27 | - "Main" 28 | - "01-Mongoose" 29 | 30 | 31 | - type: checkboxes 32 | attributes: 33 | label: "Is there an existing issue for this?" 34 | description: "Please search [here](../issues?q=is%3Aissue) to see if an issue already exists for the bug you encountered" 35 | options: 36 | - label: "I have searched the existing issues" 37 | required: true 38 | 39 | - type: textarea 40 | validations: 41 | required: true 42 | attributes: 43 | label: "Current behavior" 44 | description: "How the issue manifests?" 45 | 46 | - type: input 47 | validations: 48 | required: true 49 | attributes: 50 | label: "Minimum reproduction code" 51 | description: "An URL to some Git repository/[StackBlitz](https://stackblitz.com/fork/github/nestjs/typescript-starter)/[CodeSandbox](https://codesandbox.io/s/github/nestjs/typescript-starter/tree/master) project that reproduces your issue. [Wtf is a minimum reproduction?](https://jmcdo29.github.io/wtf-is-a-minimum-reproduction)" 52 | placeholder: "https://github.com/..." 53 | 54 | - type: textarea 55 | attributes: 56 | label: "Steps to reproduce" 57 | description: | 58 | How the issue manifests? 59 | You could leave this blank if you alread write this in your reproduction code 60 | placeholder: | 61 | 1. `npm ci` 62 | 2. `npm start:dev` 63 | 3. See error... 64 | 65 | - type: textarea 66 | validations: 67 | required: true 68 | attributes: 69 | label: "Expected behavior" 70 | description: "A clear and concise description of what you expected to happened (or code)" 71 | 72 | - type: markdown 73 | attributes: 74 | value: | 75 | --- 76 | 77 | - type: checkboxes 78 | validations: 79 | required: true 80 | attributes: 81 | label: "Package" 82 | description: | 83 | Which package (or packages) do you think your issue is related to? 84 | **Tip**: The first line of the stack trace can help you to figure out this 85 | 86 | The package isn't listed below? Try to find its repository [here](https://github.com/orgs/nestjs/repositories) and open the issue there instead 87 | options: 88 | - label: "I don't know. Or some 3rd-party package" 89 | - label: "@nestjs/common" 90 | - label: "@nestjs/core" 91 | - label: "@nestjs/microservices" 92 | - label: "@nestjs/platform-express" 93 | - label: "@nestjs/platform-fastify" 94 | - label: "@nestjs/platform-socket.io" 95 | - label: "@nestjs/platform-ws" 96 | - label: "@nestjs/testing" 97 | - label: "@nestjs/websockets" 98 | - label: "Other (see below)" 99 | 100 | - type: input 101 | attributes: 102 | label: "Other package" 103 | description: "If your issue is related to some package that is not listed above nor under @nestjs org, write its name here" 104 | 105 | - type: input 106 | attributes: 107 | label: "NestJS version" 108 | description: | 109 | Which version of `@nestjs/core` are you using? 110 | **Tip**: Make sure that all of yours `@nestjs/*` dependencies are in sync! 111 | placeholder: "8.1.3" 112 | 113 | - type: textarea 114 | validations: 115 | required: true 116 | attributes: 117 | label: "Packages versions" 118 | description: | 119 | You could leave your whole `package.json` dependencies list here, or just indicate which version of `@nestjs/*` you are using 120 | **Tip**: run _npx nest info_ 121 | value: | 122 | ```json 123 | 124 | ``` 125 | 126 | - type: input 127 | attributes: 128 | label: "Node.js version" 129 | description: "Which version of Node.js are you using?" 130 | placeholder: "14.17.6" 131 | 132 | - type: checkboxes 133 | validations: 134 | required: true 135 | attributes: 136 | label: "In which operating systems have you tested?" 137 | options: 138 | - label: macOS 139 | - label: Windows 140 | - label: Linux 141 | 142 | - type: markdown 143 | attributes: 144 | value: | 145 | --- 146 | 147 | - type: textarea 148 | attributes: 149 | label: "Other" 150 | description: | 151 | Anything else relevant? eg: Logs, OS version, IDE, package manager, etc. 152 | **Tip:** You can attach images, recordings or log files by clicking this area to highlight it and then dragging files in -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F680 Feature Request" 2 | description: "I have a suggestion \U0001F63B!" 3 | labels: ["type: enhancement :wolf:", "needs triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | ## :warning: We use GitHub Issues to track bug reports, feature requests and regressions 9 | 10 | If you are not sure that your issue is a bug, you could: 11 | 12 | - use Official[Discord community](https://discord.gg/NestJS) 13 | - use [StackOverflow using the tag `nestjs`](https://stackoverflow.com/questions/tagged/nestjs) 14 | - If it's just a quick question you can ping me on my [Twitter](https://twitter.com/CodeAshing) 15 | 16 | --- 17 | 18 | - type: dropdown 19 | validations: 20 | required: true 21 | attributes: 22 | label: "Project Name" 23 | description: "Select the project you intend to improve" 24 | options: 25 | - "Main" 26 | - "01-Mongoose" 27 | 28 | - type: checkboxes 29 | attributes: 30 | label: "Is there an existing issue that is already proposing this?" 31 | description: "Please search [here](../issues?q=is%3Aissue) to see if an issue already exists for the feature you are requesting" 32 | options: 33 | - label: "I have searched the existing issues" 34 | required: true 35 | 36 | - type: textarea 37 | validations: 38 | required: true 39 | attributes: 40 | label: "Is your feature request related to a problem? Please describe it" 41 | description: "A clear and concise description of what the problem is" 42 | placeholder: | 43 | I have an issue when ... 44 | 45 | - type: textarea 46 | validations: 47 | required: true 48 | attributes: 49 | label: "Describe the solution you'd like" 50 | description: "A clear and concise description of what you want to happen. Add any considered drawbacks" 51 | 52 | - type: textarea 53 | validations: 54 | required: true 55 | attributes: 56 | label: "Teachability, documentation, adoption, migration strategy" 57 | description: "If you can, explain how users will be able to use this and possibly write out a version the docs. Maybe a screenshot or design?" 58 | 59 | - type: textarea 60 | validations: 61 | required: true 62 | attributes: 63 | label: "What is the motivation / use case for changing the behavior?" 64 | description: "Describe the motivation or the concrete use case" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Regression.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F4A5 Regression" 2 | description: "Report an unexpected while upgrading your Nest application!" 3 | labels: ["type: bug :sob:", "needs triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | ## :warning: We use GitHub Issues to track bug reports, feature requests and regressions 9 | 10 | If you are not sure that your issue is a bug, you could: 11 | 12 | - use Official[Discord community](https://discord.gg/NestJS) 13 | - use [StackOverflow using the tag `nestjs`](https://stackoverflow.com/questions/tagged/nestjs) 14 | - If it's just a quick question you can ping me on my [Twitter](https://twitter.com/CodeAshing) 15 | 16 | **NOTE:** You don't need to answer questions that you know that aren't relevant. 17 | 18 | --- 19 | 20 | - type: dropdown 21 | validations: 22 | required: true 23 | attributes: 24 | label: "Project Name" 25 | description: "Select the project you intend to improve" 26 | options: 27 | - "Main" 28 | - "01-Mongoose" 29 | - 30 | - type: checkboxes 31 | attributes: 32 | label: "Did you read the migration guide?" 33 | description: "Check out the [migration guide here](https://docs.nestjs.com/migration-guide)!" 34 | options: 35 | - label: "I have read the whole migration guide" 36 | required: false 37 | 38 | - type: checkboxes 39 | attributes: 40 | label: "Is there an existing issue that is already proposing this?" 41 | description: "Please search [here](../issues?q=is%3Aissue) to see if an issue already exists for the feature you are requesting" 42 | options: 43 | - label: "I have searched the existing issues" 44 | required: true 45 | 46 | - type: input 47 | attributes: 48 | label: "Potential Commit/PR that introduced the regression" 49 | description: "If you have time to investigate, what PR/date/version introduced this issue" 50 | placeholder: "PR #123 or commit 5b3c4a4" 51 | 52 | - type: input 53 | validations: 54 | required: true 55 | attributes: 56 | label: "NestJS version" 57 | placeholder: "8.1.0 -> 8.1.3" 58 | 59 | - type: textarea 60 | validations: 61 | required: true 62 | attributes: 63 | label: "Describe the regression" 64 | description: "A clear and concise description of what the regression is" 65 | 66 | - type: input 67 | attributes: 68 | label: "Minimum reproduction code" 69 | description: "An URL to some git repository that reproduces this issue. [Wtf is a minimum reproduction?](https://jmcdo29.github.io/wtf-is-a-minimum-reproduction)" 70 | placeholder: "https://github.com/..." 71 | 72 | - type: textarea 73 | attributes: 74 | label: "Input code" 75 | description: "Write some code snippets if you think it is worth it" 76 | value: | 77 | ```ts 78 | 79 | ``` 80 | 81 | - type: textarea 82 | validations: 83 | required: true 84 | attributes: 85 | label: "Expected behavior" 86 | description: "A clear and concise description of what you expected to happened (or code)" 87 | 88 | - type: textarea 89 | attributes: 90 | label: "Other" 91 | description: | 92 | Anything else relevant? eg: Logs, OS version, IDE, package manager, etc. 93 | **Tip:** You can attach images, recordings or log files by clicking this area to highlight it and then dragging files in -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Suggestion_improve_performance.yml: -------------------------------------------------------------------------------- 1 | title: "perf: " 2 | name: "\U0001F525 Suggestion for Improving Performance" 3 | description: "I have a suggestion that might improve the performance of Nest \U00002728" 4 | labels: ["type: enhancement :wolf:", "needs triage"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: "Is there an existing issue that is already proposing this?" 9 | description: "Please search [here](../issues?q=is%3Aissue) to see if an issue already exists for this" 10 | options: 11 | - label: "I have searched the existing issues" 12 | required: true 13 | 14 | - type: dropdown 15 | validations: 16 | required: true 17 | attributes: 18 | label: "Project Name" 19 | description: "Select the project you intend to improve" 20 | options: 21 | - "Main" 22 | - "01-Mongoose" 23 | 24 | - type: input 25 | validations: 26 | required: true 27 | attributes: 28 | label: "NestJS version" 29 | description: "Which version do you intend to improve?" 30 | placeholder: "8.1.3" 31 | 32 | - type: textarea 33 | attributes: 34 | label: "Is your performance suggestion related to a problem? Please describe it" 35 | description: "A clear and concise description of what the problem is" 36 | 37 | - type: textarea 38 | validations: 39 | required: true 40 | attributes: 41 | label: "Describe the performance enhancement you are proposing and how we can try it out" 42 | placeholder: | 43 | Cache `array.length` on the following lines ... -------------------------------------------------------------------------------- /.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/CodeAshing/nestjs-boilerplates/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 -------------------------------------------------------------------------------- /01-mongoose/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "semi": false, 5 | "tabWidth": 2 6 | } -------------------------------------------------------------------------------- /01-mongoose/README.md: -------------------------------------------------------------------------------- 1 | # NestJS Boilerplate 2 | 3 | This is a powerful NestJS boilerplate for building scalable and enterprise-level applications. It includes robust features such as proper logging mechanisms, JWT-based authentication, token caching, and OpenAPI documentation. You can use this boilerplate as a solid foundation for projects that need to scale to millions of users. 4 | 5 | ## Getting Started 6 | 7 | Follow these simple steps to get your project up and running: 8 | 9 | ### Prerequisites 10 | 11 | Before you begin, ensure you have met the following requirements: 12 | 13 | - Node.js installed (v18+ recommended) 14 | - MongoDB installed and running 15 | - Git installed 16 | - A code editor of your choice (e.g., Visual Studio Code) 17 | 18 | ### Installation 19 | 20 | 1. Clone the repository: 21 | 22 | ```bash 23 | git clone https://github.com/CodeAshing/nestjs-boilerplates.git 24 | ``` 25 | 26 | 2. Change to the project directory: 27 | 28 | ```bash 29 | cd nestjs-boilerplates/01-mongoose 30 | ``` 31 | 32 | 3. Rename the `env.dev` file to `.env.dev` and update its values according to your configuration needs. The `.env.dev` file is used to store environment-specific variables. 33 | 34 | 4. Install project dependencies: 35 | 36 | ```bash 37 | npm install 38 | ``` 39 | 40 | ### Development 41 | 42 | To start the development server, run the following command: 43 | 44 | ```bash 45 | npm run dev 46 | ``` 47 | 48 | This will launch your NestJS application in development mode. 49 | 50 | ### Production 51 | 52 | For production deployment, you should set up your server using a process manager like PM2, Docker, or your preferred method. Remember to configure the production environment variables in a `.env.prod` file. 53 | 54 | ## Features 55 | 56 | - Proper Logging Mechanism 57 | - JWT-Based Authentication 58 | - Token Caching with Cookies 59 | - OpenAPI Swagger Documentation 60 | 61 | ## Usage 62 | 63 | This boilerplate is equipped with a simple todo app as a sample project, utilizing MongoDB as the database. You can build upon this sample or replace it with your own application logic. 64 | 65 | ## Documentation 66 | 67 | The API documentation is available through Swagger UI. Once your server is running, access it at: 68 | 69 | ``` 70 | http://localhost:4000/v1/swagger 71 | ``` 72 | 73 | ## Contributing 74 | 75 | We welcome contributions from the community. To contribute to this project, follow these steps: 76 | 77 | 1. Fork the repository. 78 | 2. Create a new branch for your feature or bug fix. 79 | 3. Make your changes and ensure tests pass. 80 | 4. Commit your changes and push to your forked repository. 81 | 5. Create a pull request to the main repository. 82 | 83 | Please review our [Contributing Guidelines](../CONTRIBUTING.md) for more details. 84 | 85 | ## License 86 | 87 | This project is licensed under the MIT License - see the [LICENSE.md](../LICENSE.md) file for details. 88 | 89 | ## Contact 90 | 91 | If you have any questions or issues, please feel free to [create an issue](https://github.com/CodeAshing/nestjs-boilerplates/issues) in our repository. 92 | -------------------------------------------------------------------------------- /01-mongoose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: . 4 | container_name: 01-mongoose 5 | ports: 6 | - 3001:3001 7 | volumes: 8 | - .:/app 9 | environment: 10 | - NODE_ENV=dev 11 | - JWT_SECRET=51654dc51e229c0c95200ba8 12 | - REFRESH_SECRET=51654dc51e229c0c95200ba8 13 | - COOKIE_SECRET=asdijfn23oq8pu54rqv 14 | - TRANSACTION_SECRET=k9i33888js8jj83j7832424 15 | - LOG_LEVEL=info 16 | - PORT=3001 17 | 18 | command: npm run dev -------------------------------------------------------------------------------- /01-mongoose/dockerfile: -------------------------------------------------------------------------------- 1 | # Base image 2 | FROM node:alpine 3 | 4 | # Set working directory 5 | WORKDIR /app 6 | 7 | #Install some dependencies 8 | COPY ./package.json /app 9 | RUN npm install --legacy-peer-deps 10 | 11 | # Copying source files 12 | COPY . /app 13 | 14 | # Running default command 15 | CMD ["npm","run","start"] -------------------------------------------------------------------------------- /01-mongoose/env.dev: -------------------------------------------------------------------------------- 1 | NODE_ENV = dev 2 | JWT_SECRET = 51654dc51e229c0c95200ba8 3 | REFRESH_SECRET = 51654dc51e229c0c95200ba8 4 | COOKIE_SECRET = asdijfn23oq8pu54rqv 5 | TRANSACTION_SECRET = k9i33888js8jj83j7832424 6 | LOG_LEVEL = info 7 | PORT = 4000 8 | DATABASE_URI = {DATABASE_URI}/{DATABASE_NAME} 9 | TOKEN_EXPIRES_DURATION_IN_MINUTES_FOR_EMPLOYEE = 60 10 | TOKEN_EXPIRES_DURATION_IN_MINUTES_FOR_CLIENT = 60 11 | CACHE_EXPIRES_DURATION_IN_MINUTES = 60 12 | CLIENT_OTP_VALIDITY_DURATION_IN_MINUTES = 30 13 | CLIENT_TRANSACTION_VALIDITY_DURATION_IN_HOURS = 12 14 | REFRESH_EXPIRES_DURATION_IN_YEARS = 1 15 | CLIENT_DOMAIN = example.com 16 | COOKIE_SECRET_EXPIRES_DURATION_IN_MINUTES = 60 -------------------------------------------------------------------------------- /01-mongoose/mongoose.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "e8a2548a-c164-40f5-aad6-27ca2ad44eb8", 4 | "name": "mongoose", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "27835984" 7 | }, 8 | "item": [ 9 | { 10 | "name": "auth", 11 | "item": [ 12 | { 13 | "name": "register", 14 | "request": { 15 | "method": "POST", 16 | "header": [], 17 | "body": { 18 | "mode": "raw", 19 | "raw": "{\n \"email\": \"asharibb@gmail.com\",\n \"name\": \"Asharib Ahmed\",\n \"phone\": \"+923353011417\",\n \"address\": \"xyz\",\n \"role\": \"ADMIN\",\n \"password\": \"hello\"\n}", 20 | "options": { 21 | "raw": { 22 | "language": "json" 23 | } 24 | } 25 | }, 26 | "url": { 27 | "raw": "{{local}}/v1/auth/register", 28 | "host": [ 29 | "{{local}}" 30 | ], 31 | "path": [ 32 | "v1", 33 | "auth", 34 | "register" 35 | ] 36 | } 37 | }, 38 | "response": [] 39 | }, 40 | { 41 | "name": "login", 42 | "request": { 43 | "method": "POST", 44 | "header": [], 45 | "body": { 46 | "mode": "raw", 47 | "raw": "{\n \"email\": \"asharibb@gmail.com\",\n \"password\": \"hello\"\n}", 48 | "options": { 49 | "raw": { 50 | "language": "json" 51 | } 52 | } 53 | }, 54 | "url": { 55 | "raw": "{{local}}/v1/auth/login", 56 | "host": [ 57 | "{{local}}" 58 | ], 59 | "path": [ 60 | "v1", 61 | "auth", 62 | "login" 63 | ] 64 | } 65 | }, 66 | "response": [] 67 | }, 68 | { 69 | "name": "logout", 70 | "request": { 71 | "method": "GET", 72 | "header": [], 73 | "url": { 74 | "raw": "{{local}}/v1/auth/logout", 75 | "host": [ 76 | "{{local}}" 77 | ], 78 | "path": [ 79 | "v1", 80 | "auth", 81 | "logout" 82 | ] 83 | } 84 | }, 85 | "response": [] 86 | } 87 | ] 88 | }, 89 | { 90 | "name": "user", 91 | "item": [ 92 | { 93 | "name": "user details", 94 | "request": { 95 | "method": "GET", 96 | "header": [], 97 | "url": { 98 | "raw": "{{local}}/v1/user", 99 | "host": [ 100 | "{{local}}" 101 | ], 102 | "path": [ 103 | "v1", 104 | "user" 105 | ] 106 | } 107 | }, 108 | "response": [] 109 | }, 110 | { 111 | "name": "delete user", 112 | "request": { 113 | "method": "DELETE", 114 | "header": [], 115 | "body": { 116 | "mode": "raw", 117 | "raw": "{\n \"email\":\"asharibahmed143@gmail.com\"\n}", 118 | "options": { 119 | "raw": { 120 | "language": "json" 121 | } 122 | } 123 | }, 124 | "url": { 125 | "raw": "{{local}}/v1/user", 126 | "host": [ 127 | "{{local}}" 128 | ], 129 | "path": [ 130 | "v1", 131 | "user" 132 | ] 133 | } 134 | }, 135 | "response": [] 136 | }, 137 | { 138 | "name": "all user details", 139 | "request": { 140 | "method": "GET", 141 | "header": [], 142 | "url": { 143 | "raw": "{{local}}/v1/user/get-all-users", 144 | "host": [ 145 | "{{local}}" 146 | ], 147 | "path": [ 148 | "v1", 149 | "user", 150 | "get-all-users" 151 | ] 152 | } 153 | }, 154 | "response": [] 155 | }, 156 | { 157 | "name": "update User", 158 | "request": { 159 | "method": "PUT", 160 | "header": [], 161 | "body": { 162 | "mode": "raw", 163 | "raw": "{\n \"name\": \"Asharib Ahmed\",\n \"phone\": \"+923353011417\",\n \"address\": \"xyzz\"\n}", 164 | "options": { 165 | "raw": { 166 | "language": "json" 167 | } 168 | } 169 | }, 170 | "url": { 171 | "raw": "{{local}}/v1/user", 172 | "host": [ 173 | "{{local}}" 174 | ], 175 | "path": [ 176 | "v1", 177 | "user" 178 | ] 179 | } 180 | }, 181 | "response": [] 182 | } 183 | ] 184 | }, 185 | { 186 | "name": "todo", 187 | "item": [ 188 | { 189 | "name": "get todo", 190 | "request": { 191 | "method": "GET", 192 | "header": [], 193 | "url": { 194 | "raw": "{{local}}/v1/todo", 195 | "host": [ 196 | "{{local}}" 197 | ], 198 | "path": [ 199 | "v1", 200 | "todo" 201 | ] 202 | } 203 | }, 204 | "response": [] 205 | }, 206 | { 207 | "name": "add todo", 208 | "request": { 209 | "method": "POST", 210 | "header": [], 211 | "body": { 212 | "mode": "raw", 213 | "raw": "{\n \"title\": \"hello\",\n \"description\": \"description\"\n}", 214 | "options": { 215 | "raw": { 216 | "language": "json" 217 | } 218 | } 219 | }, 220 | "url": { 221 | "raw": "{{local}}/v1/todo", 222 | "host": [ 223 | "{{local}}" 224 | ], 225 | "path": [ 226 | "v1", 227 | "todo" 228 | ] 229 | } 230 | }, 231 | "response": [] 232 | }, 233 | { 234 | "name": "delete todo", 235 | "request": { 236 | "method": "DELETE", 237 | "header": [], 238 | "body": { 239 | "mode": "raw", 240 | "raw": "{\n \"id\": \"6500cc53a7b91b788717134f\"\n}", 241 | "options": { 242 | "raw": { 243 | "language": "json" 244 | } 245 | } 246 | }, 247 | "url": { 248 | "raw": "{{local}}/v1/todo", 249 | "host": [ 250 | "{{local}}" 251 | ], 252 | "path": [ 253 | "v1", 254 | "todo" 255 | ] 256 | } 257 | }, 258 | "response": [] 259 | } 260 | ] 261 | } 262 | ], 263 | "event": [ 264 | { 265 | "listen": "prerequest", 266 | "script": { 267 | "type": "text/javascript", 268 | "exec": [ 269 | "" 270 | ] 271 | } 272 | }, 273 | { 274 | "listen": "test", 275 | "script": { 276 | "type": "text/javascript", 277 | "exec": [ 278 | "" 279 | ] 280 | } 281 | } 282 | ], 283 | "variable": [ 284 | { 285 | "key": "local", 286 | "value": "http://localhost:4000", 287 | "type": "string" 288 | } 289 | ] 290 | } -------------------------------------------------------------------------------- /01-mongoose/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /01-mongoose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01-mongoose", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "vite": "vite", 11 | "build": "nest build", 12 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 13 | "start": "nest start", 14 | "dev": "dotenv -e .env.dev -- nest start --watch", 15 | "start:debug": "dotenv -e .env.prod -- nest start --debug --watch", 16 | "start:prod": "dotenv -e .env.prod -- node dist/main", 17 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 18 | "test": "jest", 19 | "test:watch": "jest --watch", 20 | "test:cov": "jest --coverage", 21 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 22 | "test:e2e": "jest --config ./test/jest-e2e.json", 23 | "deploy:local": "node ./node_modules/serverless/bin/serverless.js offline" 24 | }, 25 | "lint-staged": { 26 | "*.ts": [ 27 | "eslint --fix" 28 | ] 29 | }, 30 | "dependencies": { 31 | "@nestjs/cache-manager": "^2.1.0", 32 | "@nestjs/class-transformer": "^0.4.0", 33 | "@nestjs/class-validator": "^0.13.4", 34 | "@nestjs/common": "^10.2.4", 35 | "@nestjs/config": "^3.0.1", 36 | "@nestjs/core": "^10.2.4", 37 | "@nestjs/jwt": "^10.1.1", 38 | "@nestjs/mongoose": "^10.0.1", 39 | "@nestjs/passport": "^10.0.1", 40 | "@nestjs/swagger": "^7.1.10", 41 | "@types/passport-jwt": "^3.0.9", 42 | "bcrypt": "^5.1.1", 43 | "cache-manager": "^5.2.3", 44 | "class-transformer": "^0.5.1", 45 | "class-validator": "^0.14.0", 46 | "cookie-parser": "^1.4.6", 47 | "helmet": "^7.0.0", 48 | "jsonwebtoken": "^9.0.2", 49 | "moment-timezone": "^0.5.43", 50 | "mongoose": "^7.5.0", 51 | "passport": "^0.6.0", 52 | "passport-jwt": "^4.0.1", 53 | "rxjs": "^7.8.1", 54 | "swagger-ui-express": "^5.0.0", 55 | "webpack": "^5.88.2", 56 | "winston": "^3.10.0" 57 | }, 58 | "devDependencies": { 59 | "@nestjs/cli": "^10.1.17", 60 | "@nestjs/schematics": "^10.0.2", 61 | "@nestjs/testing": "^10.2.4", 62 | "@types/bcrypt": "^5.0.0", 63 | "@types/cookie-parser": "^1.4.4", 64 | "@types/express": "^4.17.17", 65 | "@types/jest": "29.5.4", 66 | "@types/node": "^20.6.0", 67 | "@types/supertest": "^2.0.12", 68 | "@typescript-eslint/eslint-plugin": "^6.6.0", 69 | "@typescript-eslint/parser": "^6.6.0", 70 | "dotenv-cli": "^7.3.0", 71 | "eslint": "^8.49.0", 72 | "eslint-config-prettier": "^9.0.0", 73 | "eslint-plugin-prettier": "^5.0.0", 74 | "jest": "29.6.4", 75 | "supertest": "^6.3.3", 76 | "ts-jest": "29.1.1", 77 | "ts-loader": "^9.4.4", 78 | "ts-node": "^10.9.1", 79 | "tsconfig-paths": "4.2.0", 80 | "typescript": "^5.2.2" 81 | }, 82 | "jest": { 83 | "moduleFileExtensions": [ 84 | "js", 85 | "json", 86 | "ts" 87 | ], 88 | "rootDir": "src", 89 | "testRegex": ".*\\.spec\\.ts$", 90 | "transform": { 91 | "^.+\\.(t|j)s$": "ts-jest" 92 | }, 93 | "collectCoverageFrom": [ 94 | "**/*.(t|j)s" 95 | ], 96 | "coverageDirectory": "../coverage", 97 | "testEnvironment": "node" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /01-mongoose/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common' 2 | import { AppService } from './app.service' 3 | @Controller() 4 | export class AppController { 5 | constructor(private readonly appService: AppService) {} 6 | 7 | @Get() 8 | getHello(): Promise { 9 | return this.appService.getHello() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /01-mongoose/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { DatabaseModule } from 'src/database/database.module' 2 | import { Module } from '@nestjs/common' 3 | import { AppController } from './app.controller' 4 | import { AppService } from './app.service' 5 | import * as dotenv from 'dotenv' 6 | 7 | import { CacheModule } from '@nestjs/cache-manager' 8 | dotenv.config() 9 | 10 | import { TodoModule, UsersModule } from './app/modules' 11 | import { AuthModule } from './app/auth/auth.module' 12 | import { ConfigModule } from './config/config.module' 13 | import { LoggerModule } from './logger/logger.module' 14 | 15 | @Module({ 16 | imports: [ 17 | // --- Settings --- 18 | ConfigModule, 19 | LoggerModule, // --- Logger --- 20 | CacheModule.register({ 21 | isGlobal: true, 22 | }), 23 | // --- Connections --- 24 | DatabaseModule.forRoot(), 25 | 26 | // --- Modules --- 27 | AuthModule, 28 | UsersModule, 29 | TodoModule, 30 | ], 31 | controllers: [AppController], 32 | providers: [AppService], 33 | }) 34 | export class AppModule {} 35 | -------------------------------------------------------------------------------- /01-mongoose/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | @Injectable() 4 | export class AppService { 5 | constructor() {} 6 | async getHello(): Promise { 7 | return 'Hello World' 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Req, 6 | Post, 7 | Res, 8 | HttpCode, 9 | UseGuards, 10 | } from '@nestjs/common' 11 | import { Response, Request } from 'express' 12 | import { GetUser, ResponseMessage } from '../common/decorator' 13 | import { AuthService } from './auth.service' 14 | import { LoginDTO, RegisterDTO } from './dto' 15 | import { JwtGuard, RefreshTokenGuard } from './guard' 16 | import { ApiResponse, ApiTags, ApiCookieAuth } from '@nestjs/swagger' 17 | import { responseEnum } from './enum' 18 | import { User } from '../modules/user/schema' 19 | 20 | @Controller('auth') 21 | @ApiTags('auth') 22 | export class AuthController { 23 | constructor(private readonly authService: AuthService) {} 24 | 25 | @Post('login') 26 | @ResponseMessage(responseEnum.LOGIN) 27 | @ApiResponse({ 28 | status: 200, 29 | description: responseEnum.LOGIN, 30 | }) 31 | @HttpCode(200) 32 | async login( 33 | @Res({ passthrough: true }) response: Response, 34 | @Body() payload: LoginDTO, 35 | ): Promise { 36 | return await this.authService.login(response, payload) 37 | } 38 | 39 | @UseGuards(JwtGuard) 40 | @UseGuards(RefreshTokenGuard) 41 | @ResponseMessage(responseEnum.TOKEN_REFRESH_SUCCESSFULLY) 42 | @ApiResponse({ 43 | status: 200, 44 | description: responseEnum.TOKEN_REFRESH_SUCCESSFULLY, 45 | }) 46 | @Get('refresh-token') 47 | @HttpCode(200) 48 | async tokenRefresh( 49 | @GetUser() { email }: User, 50 | @Res({ passthrough: true }) response: Response, 51 | ): Promise { 52 | return await this.authService.tokenRefresh(email, response) 53 | } 54 | 55 | @UseGuards(JwtGuard) 56 | @ApiCookieAuth('api-auth') 57 | @Get('logout') 58 | @ResponseMessage(responseEnum.LOGOUT) 59 | @ApiResponse({ 60 | status: 200, 61 | description: responseEnum.LOGOUT, 62 | }) 63 | @HttpCode(200) 64 | async logout( 65 | @GetUser() { email }: User, 66 | @Req() request: Request, 67 | @Res({ passthrough: true }) response: Response, 68 | ): Promise { 69 | return await this.authService.logout(email, request, response) 70 | } 71 | 72 | @Post('register') 73 | @ResponseMessage(responseEnum.REGISTER) 74 | @ApiResponse({ 75 | status: 200, 76 | description: responseEnum.REGISTER, 77 | }) 78 | @HttpCode(200) 79 | async register( 80 | @Res({ passthrough: true }) response: Response, 81 | @Body() body: RegisterDTO, 82 | ): Promise { 83 | return await this.authService.register(body, response) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { JwtStrategy, RefreshTokenStrategy } from './guard' 2 | import { Module } from '@nestjs/common' 3 | import { AuthController } from './auth.controller' 4 | import { AuthService } from './auth.service' 5 | import { MongooseModule } from '@nestjs/mongoose' 6 | 7 | import { ConfigModule } from 'src/config/config.module' 8 | import { connectionEnum } from 'src/app/common/enum' 9 | 10 | import { CacheModule } from '@nestjs/cache-manager' 11 | import { Helper } from 'src/app/common/helper/utilities.helper' 12 | import { JwtModule } from '@nestjs/jwt' 13 | import { User, UserSchema } from '../modules/user/schema' 14 | 15 | @Module({ 16 | imports: [ 17 | ConfigModule, 18 | JwtModule.register({}), 19 | MongooseModule.forFeature( 20 | [{ name: User.name, schema: UserSchema }], 21 | connectionEnum.database, 22 | ), 23 | ], 24 | controllers: [AuthController], 25 | providers: [AuthService, RefreshTokenStrategy, JwtStrategy, Helper], 26 | exports: [AuthService], 27 | }) 28 | export class AuthModule {} 29 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { RoleEnum } from '../common/enum' 2 | import { 3 | ConflictException, 4 | ForbiddenException, 5 | Inject, 6 | Injectable, 7 | Logger, 8 | UnauthorizedException, 9 | } from '@nestjs/common' 10 | 11 | import { CACHE_MANAGER } from '@nestjs/cache-manager' 12 | import { InjectModel } from '@nestjs/mongoose' 13 | import { Model, Types } from 'mongoose' 14 | import * as moment from 'moment-timezone' 15 | moment.tz.setDefault('Asia/Karachi') 16 | 17 | import { Response, Request } from 'express' 18 | import { connectionEnum } from 'src/app/common/enum' 19 | 20 | import { JwtService } from '@nestjs/jwt' 21 | import { ConfigService } from 'src/config/config.service' 22 | import { IUserToken } from './interface' 23 | 24 | import { LoginDTO, RegisterDTO } from './dto' 25 | import { Cache } from 'cache-manager' 26 | import { User, UserDocument } from '../modules/user/schema' 27 | 28 | import { Helper } from 'src/app/common/helper/utilities.helper' 29 | import { responseEnum } from './enum' 30 | const helper = new Helper() 31 | @Injectable() 32 | export class AuthService { 33 | private logger = new Logger('AuthService') 34 | 35 | constructor( 36 | private readonly helper: Helper, 37 | private config: ConfigService, 38 | private jwt: JwtService, 39 | 40 | @Inject(CACHE_MANAGER) private cacheManager: Cache, 41 | 42 | @InjectModel(User.name, connectionEnum.database) 43 | private readonly userModel: Model, 44 | ) {} 45 | 46 | async register(body: RegisterDTO, response: Response): Promise { 47 | this.logger.log('Hits register() Method') 48 | 49 | const userData = await this.userModel.findOne({ 50 | email: body.email, 51 | }) 52 | 53 | if (userData) throw new ConflictException(responseEnum.USER_ALREADY_EXIST) 54 | 55 | const hash = await this.helper.encryptPassword(body.password) 56 | 57 | const accessTokenPayload = { 58 | user: body.role, 59 | email: body.email, 60 | type: 'access', 61 | } 62 | 63 | const accessToken = await this.signToken( 64 | accessTokenPayload, 65 | this.config.get().tokenExpiresDurationInMinutesForEmployee + 'm', 66 | this.config.get().jwtSecret, 67 | ) 68 | 69 | const refreshTokenPayload = { 70 | user: body.role, 71 | email: body.email, 72 | type: 'refresh', 73 | } 74 | 75 | const refreshToken = await this.signToken( 76 | refreshTokenPayload, 77 | this.config.get().refreshExpiresDurationInYears + 'Y', 78 | this.config.get().refreshSecret, 79 | ) 80 | 81 | response.cookie( 82 | 'api-auth', 83 | { accessToken, refreshToken }, 84 | { 85 | secure: true, 86 | httpOnly: true, 87 | expires: new Date( 88 | Number(new Date()) + 89 | this.config.get().cookieSecretExpiresDurationInMinutes * 60 * 1000, 90 | ), 91 | signed: true, 92 | }, 93 | ) 94 | 95 | await this.userModel.create({ 96 | ...body, 97 | password: hash, 98 | }) 99 | 100 | return null 101 | } 102 | 103 | async login(response: Response, body: LoginDTO): Promise { 104 | this.logger.log('Hits login() Method') 105 | const { email, password } = body 106 | 107 | const userData = await this.userModel.findOne({ 108 | email, 109 | }) 110 | 111 | if (!userData) throw new ForbiddenException(responseEnum.INVALID_CREDENTIAL) 112 | 113 | const isMatched = await this.helper.comparePassword( 114 | password, 115 | userData.password, 116 | ) 117 | 118 | if (!isMatched) 119 | throw new ForbiddenException(responseEnum.INVALID_CREDENTIAL) 120 | 121 | const accessTokenPayload = { 122 | user: userData.role, 123 | email: body.email, 124 | type: 'access', 125 | } 126 | 127 | const accessToken = await this.signToken( 128 | accessTokenPayload, 129 | this.config.get().tokenExpiresDurationInMinutesForEmployee + 'm', 130 | this.config.get().jwtSecret, 131 | ) 132 | 133 | const refreshTokenPayload = { 134 | user: userData.role, 135 | email: body.email, 136 | type: 'refresh', 137 | } 138 | 139 | const refreshToken = await this.signToken( 140 | refreshTokenPayload, 141 | this.config.get().refreshExpiresDurationInYears + 'Y', 142 | this.config.get().refreshSecret, 143 | ) 144 | 145 | response.cookie( 146 | 'api-auth', 147 | { accessToken, refreshToken }, 148 | { 149 | secure: true, 150 | httpOnly: true, 151 | expires: new Date( 152 | Number(new Date()) + 153 | this.config.get().cookieSecretExpiresDurationInMinutes * 60 * 1000, 154 | ), 155 | signed: true, 156 | }, 157 | ) 158 | 159 | return null 160 | } 161 | 162 | async logout( 163 | email: string, 164 | request: Request, 165 | response: Response, 166 | ): Promise { 167 | const authToken = request.signedCookies['api-auth'].accessToken 168 | 169 | const cacheUserRecord = (await this.cacheManager.get(email)) ?? [] 170 | 171 | cacheUserRecord.push(authToken) 172 | 173 | await this.cacheManager.set( 174 | email, 175 | cacheUserRecord, 176 | this.config.get().cacheExpiresDurationInMinutes * 60 * 60, 177 | ) 178 | 179 | response.clearCookie('api-auth') 180 | 181 | this.logger.log(`User ${email} logged out successfully`) 182 | 183 | return null 184 | } 185 | 186 | async signToken( 187 | payload: IUserToken, 188 | expiresIn: string, 189 | secret: string, 190 | ): Promise { 191 | const token = await this.jwt.signAsync(payload, { 192 | expiresIn: expiresIn, 193 | secret: secret, 194 | issuer: this.config.get().clientDomain, 195 | }) 196 | 197 | return token 198 | } 199 | 200 | async tokenRefresh(email: string, response: Response): Promise { 201 | this.logger.log('Hits tokenRefresh()') 202 | 203 | const accessTokenPayload = { 204 | user: RoleEnum.ADMIN, 205 | email: email, 206 | type: 'access', 207 | } 208 | 209 | const accessToken = await this.signToken( 210 | accessTokenPayload, 211 | this.config.get().tokenExpiresDurationInMinutesForEmployee + 'm', 212 | this.config.get().jwtSecret, 213 | ) 214 | 215 | const refreshTokenPayload = { 216 | user: RoleEnum.USER, 217 | email: email, 218 | type: 'refresh', 219 | } 220 | 221 | const refreshToken = await this.signToken( 222 | refreshTokenPayload, 223 | this.config.get().refreshExpiresDurationInYears + 'Y', 224 | this.config.get().refreshSecret, 225 | ) 226 | 227 | response.cookie( 228 | 'api-auth', 229 | { accessToken, refreshToken }, 230 | { 231 | secure: true, 232 | httpOnly: true, 233 | expires: new Date( 234 | Number(new Date()) + 235 | this.config.get().cookieSecretExpiresDurationInMinutes * 60 * 1000, 236 | ), 237 | signed: true, 238 | }, 239 | ) 240 | } 241 | 242 | async validateToken(token: string, payload: IUserToken): Promise { 243 | this.logger.log('Hits validateEmployeeToken()') 244 | 245 | const user = await this.userModel 246 | .findOne({ 247 | email: payload.email, 248 | }) 249 | .select({ password: 0 }) 250 | 251 | if (!user) throw new ForbiddenException(responseEnum.NOT_AUTHORIZED) 252 | 253 | const cacheUser = await this.cacheManager.get(payload.email) 254 | 255 | if (cacheUser?.includes(token)) 256 | throw new UnauthorizedException(responseEnum.SESSION_EXPIRED) 257 | 258 | return user 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from './register.dto' 2 | export * from './login.dto' 3 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/dto/login.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, Matches } from '@nestjs/class-validator' 2 | import { ApiProperty } from '@nestjs/swagger' 3 | 4 | export class LoginDTO { 5 | @IsNotEmpty({ message: 'Please provide email' }) 6 | @ApiProperty({ description: 'body', example: 'test@example.com' }) 7 | email: string 8 | 9 | @IsNotEmpty({ message: 'Please provide password' }) 10 | @ApiProperty({ description: 'Password', example: '123456' }) 11 | @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { 12 | message: 'Password is too weak', 13 | }) 14 | password: string 15 | } 16 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/dto/register.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from '@nestjs/class-validator' 2 | import { ApiProperty } from '@nestjs/swagger' 3 | import { IsEmail, IsEnum, IsOptional, IsString, Matches } from 'class-validator' 4 | import { RoleEnum } from 'src/app/common/enum' 5 | 6 | export class RegisterDTO { 7 | @ApiProperty({ description: 'email', example: 'example@test.com' }) 8 | @IsEmail() 9 | @IsNotEmpty() 10 | email: string 11 | 12 | @ApiProperty({ description: 'name', example: 'test' }) 13 | @IsNotEmpty() 14 | @IsString() 15 | name: string 16 | 17 | @ApiProperty({ description: 'phone', example: '+923350000000' }) 18 | @IsOptional() 19 | @IsNotEmpty() 20 | @IsString() 21 | phone: string 22 | 23 | @ApiProperty({ description: 'address', example: 'H#ABC' }) 24 | @IsOptional() 25 | @IsNotEmpty() 26 | @IsString() 27 | address: string 28 | 29 | @ApiProperty({ description: 'role', example: RoleEnum }) 30 | @IsNotEmpty() 31 | @IsEnum(RoleEnum) 32 | role: RoleEnum 33 | 34 | @ApiProperty({ description: 'password', example: `Opt1m1st1(` }) 35 | @IsNotEmpty() 36 | @IsString() 37 | @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { 38 | message: 'Password is too weak', 39 | }) 40 | password: string 41 | } 42 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/enum/index.ts: -------------------------------------------------------------------------------- 1 | export * from './response.enum' 2 | export * from './token.enum' 3 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/enum/response.enum.ts: -------------------------------------------------------------------------------- 1 | export enum responseEnum { 2 | LOGOUT = 'You are logout', 3 | LOGIN = 'login successfully', 4 | NOT_AUTHORIZED = 'You are not authorized to perform this action', 5 | SESSION_EXPIRED = 'Session expired', 6 | INVALID_CREDENTIAL = 'Invalid credential', 7 | TOKEN_REFRESH_SUCCESSFULLY = 'Token refresh successfully', 8 | TOKEN_REFRESH_FAILED = 'Token refresh failed', 9 | REGISTER = 'You have register successfully', 10 | USER_ALREADY_EXIST = 'User already exist with this email', 11 | } 12 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/enum/token.enum.ts: -------------------------------------------------------------------------------- 1 | export enum TokenEnum { 2 | ACCESS = 'Access', 3 | REFRESH = 'Refresh', 4 | } 5 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/guard/index.ts: -------------------------------------------------------------------------------- 1 | export * from './jwt.guard' 2 | export * from './jwt.strategy' 3 | export * from './roles.guard' 4 | export * from './refresh-token.guard' 5 | export * from './refresh-token.strategy' 6 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/guard/jwt.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { AuthGuard } from '@nestjs/passport' 3 | 4 | @Injectable() 5 | export class JwtGuard extends AuthGuard('jwt') {} 6 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/guard/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { RoleEnum } from '../../common/enum/roles.enum' 2 | import { AuthService } from 'src/app/auth/auth.service' 3 | import { Injectable, UnauthorizedException } from '@nestjs/common' 4 | import { PassportStrategy } from '@nestjs/passport' 5 | import { Request } from 'express' 6 | import { ConfigService } from 'src/config/config.service' 7 | import { Strategy } from 'passport-jwt' 8 | import { responseEnum } from '../enum' 9 | import { JWTPayload } from '../interface' 10 | 11 | @Injectable() 12 | export class JwtStrategy extends PassportStrategy(Strategy) { 13 | constructor( 14 | private configService: ConfigService, 15 | private authService: AuthService, 16 | ) { 17 | super({ 18 | jwtFromRequest: (req: Request): string | null => { 19 | if (req.signedCookies['api-auth']) 20 | return req.signedCookies['api-auth'].accessToken ?? null 21 | }, 22 | ignoreExpiration: false, 23 | maxAge: '7d', 24 | passReqToCallback: true, 25 | secretOrKey: configService.get().jwtSecret, 26 | }) 27 | } 28 | 29 | async validate(req: Request, payload: JWTPayload) { 30 | if (!payload) throw new UnauthorizedException(responseEnum.NOT_AUTHORIZED) 31 | 32 | const token = req.signedCookies['api-auth'].accessToken 33 | const data = await this.authService.validateToken(token, payload) 34 | 35 | if (!data) throw new UnauthorizedException(responseEnum.NOT_AUTHORIZED) 36 | 37 | return data 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/guard/refresh-token.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { AuthGuard } from '@nestjs/passport' 3 | 4 | @Injectable() 5 | export class RefreshTokenGuard extends AuthGuard('RefreshTokenStrategy') {} 6 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/guard/refresh-token.strategy.ts: -------------------------------------------------------------------------------- 1 | import { RoleEnum } from '../../common/enum/roles.enum' 2 | import { AuthService } from 'src/app/auth/auth.service' 3 | import { Inject, Injectable, UnauthorizedException } from '@nestjs/common' 4 | import { CACHE_MANAGER } from '@nestjs/cache-manager' 5 | import { Cache } from 'cache-manager' 6 | import { PassportStrategy } from '@nestjs/passport' 7 | import { ConfigService } from 'src/config/config.service' 8 | import { Strategy } from 'passport-jwt' 9 | import { Request } from 'express' 10 | import { responseEnum } from '../enum' 11 | import { JWTPayload } from '../interface' 12 | 13 | @Injectable() 14 | export class RefreshTokenStrategy extends PassportStrategy( 15 | Strategy, 16 | 'RefreshTokenStrategy', 17 | ) { 18 | constructor( 19 | @Inject(CACHE_MANAGER) private cacheManager: Cache, 20 | 21 | private configService: ConfigService, 22 | private authService: AuthService, 23 | ) { 24 | super({ 25 | jwtFromRequest: (req: Request): string | null => { 26 | if (req.signedCookies['api-auth']) 27 | return req.signedCookies['api-auth'].refreshToken ?? null 28 | }, 29 | ignoreExpiration: false, 30 | maxAge: '7d', 31 | passReqToCallback: true, 32 | secretOrKey: configService.get().refreshSecret, 33 | }) 34 | } 35 | 36 | async validate(request: Request, payload: JWTPayload) { 37 | if (!payload) throw new UnauthorizedException(responseEnum.NOT_AUTHORIZED) 38 | 39 | const token = request.signedCookies['api-auth'].accessToken 40 | if (!token) throw new UnauthorizedException(responseEnum.NOT_AUTHORIZED) 41 | 42 | const addToCache = async (key: string, value: string) => { 43 | const cacheUserRecord = (await this.cacheManager.get(key)) ?? [] 44 | 45 | cacheUserRecord.push(value) 46 | 47 | await this.cacheManager.set( 48 | key, 49 | cacheUserRecord, 50 | this.configService.get().cacheExpiresDurationInMinutes * 60 * 60, 51 | ) 52 | } 53 | 54 | const data = await this.authService.validateToken(token, payload) 55 | 56 | if (!data) throw new UnauthorizedException(responseEnum.NOT_AUTHORIZED) 57 | 58 | await addToCache(payload.email, token) 59 | return data 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/guard/roles.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common' 2 | import { Reflector } from '@nestjs/core' 3 | import { ROLES_KEY } from 'src/app/common/decorator' 4 | import { RoleEnum } from 'src/app/common/enum' 5 | 6 | @Injectable() 7 | export class RolesGuard implements CanActivate { 8 | constructor(private reflector: Reflector) {} 9 | 10 | canActivate(context: ExecutionContext): boolean { 11 | const requiredRoles = this.reflector.getAllAndMerge(ROLES_KEY, [ 12 | context.getHandler(), 13 | context.getClass(), 14 | ]) 15 | 16 | if (!requiredRoles || requiredRoles.length === 0) return false 17 | 18 | const { user } = context.switchToHttp().getRequest() 19 | 20 | return requiredRoles.some((role) => user.role === role) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/interface/index.ts: -------------------------------------------------------------------------------- 1 | export * from './token-sign-in.interface' 2 | export * from './jwt-payload.interface' 3 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/interface/jwt-payload.interface.ts: -------------------------------------------------------------------------------- 1 | import { RoleEnum } from 'src/app/common/enum' 2 | 3 | export interface JWTPayload { 4 | user: RoleEnum 5 | email: string 6 | type: string 7 | iat: number 8 | exp: number 9 | iss: string 10 | } 11 | -------------------------------------------------------------------------------- /01-mongoose/src/app/auth/interface/token-sign-in.interface.ts: -------------------------------------------------------------------------------- 1 | import { RoleEnum } from 'src/app/common/enum' 2 | 3 | export interface IUserToken { 4 | user: RoleEnum 5 | email: string 6 | type: string 7 | } 8 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/decorator/array-of-object.decorator.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer' 2 | import { 3 | IsString, 4 | registerDecorator, 5 | ValidateNested, 6 | ValidationArguments, 7 | ValidationOptions, 8 | } from 'class-validator' 9 | 10 | export function IsArrayOfObjects(validationOptions?: ValidationOptions) { 11 | return (object: unknown, propertyName: string) => { 12 | registerDecorator({ 13 | name: 'IsArrayOfObjects', 14 | target: object.constructor, 15 | propertyName, 16 | constraints: [], 17 | options: validationOptions, 18 | validator: { 19 | validate(value: any): boolean { 20 | return ( 21 | Array.isArray(value) && 22 | value.every( 23 | (element: any) => 24 | element instanceof Object && !(element instanceof Array), 25 | ) 26 | ) 27 | }, 28 | defaultMessage: (validationArguments?: ValidationArguments): string => 29 | `${validationArguments.property} must be an array of objects`, 30 | }, 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/decorator/get-user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common' 2 | 3 | export const GetUser = createParamDecorator( 4 | (data: string | undefined, ctx: ExecutionContext) => { 5 | const request: Express.Request = ctx.switchToHttp().getRequest() 6 | //if data found then return the client email else return the payload 7 | if (data) { 8 | return request['user'][data] 9 | } 10 | return request['user'] 11 | }, 12 | ) 13 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/decorator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './get-user.decorator' 2 | export * from './response.decorator' 3 | export * from './roles.decorator' 4 | export * from './array-of-object.decorator' 5 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/decorator/response.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common' 2 | 3 | export const ResponseMessageKey = 'ResponseMessageKey' 4 | export const ResponseMessage = (message: string) => 5 | SetMetadata(ResponseMessageKey, message) 6 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/decorator/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common' 2 | 3 | export const ROLES_KEY = 'roles' 4 | export const Roles = (roles: any) => SetMetadata(ROLES_KEY, roles) 5 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/enum/connections.enum.ts: -------------------------------------------------------------------------------- 1 | export enum connectionEnum { 2 | database = 'database', 3 | } 4 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/enum/index.ts: -------------------------------------------------------------------------------- 1 | export * from './roles.enum' 2 | export * from './connections.enum' 3 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/enum/roles.enum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | USER = 'USER', 3 | ADMIN = 'ADMIN', 4 | } 5 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/filter/exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentsHost, 3 | Catch, 4 | ExceptionFilter, 5 | HttpException, 6 | HttpStatus, 7 | } from '@nestjs/common' 8 | 9 | @Catch() 10 | export class HttpExceptionFilter implements ExceptionFilter { 11 | catch(exception: unknown, host: ArgumentsHost) { 12 | //log in the console 13 | 14 | // console.log({ exception }); 15 | 16 | const ctx = host.switchToHttp() 17 | const response = ctx.getResponse() 18 | const request = ctx.getRequest() 19 | 20 | const status = 21 | exception instanceof HttpException 22 | ? exception.getStatus() 23 | : HttpStatus.INTERNAL_SERVER_ERROR 24 | const errorResponse = { 25 | status: 'error', 26 | code: status, 27 | timestamp: new Date().toISOString(), 28 | path: request.url, 29 | method: request.method, 30 | message: 31 | status !== HttpStatus.INTERNAL_SERVER_ERROR 32 | ? exception!['response']!['message'] || 33 | exception!['message'] || 34 | exception!['message']!['error'] || 35 | null 36 | : exception!['message'] || 37 | exception!['response']!['message'] || 38 | exception!['message']!['error'] || 39 | 'Internal server error', 40 | } 41 | 42 | // clear the cookie if the user is not authenticated 43 | if (status === 401) response.clearCookie('api-auth') 44 | 45 | response.status(status).json(errorResponse) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/filter/index.ts: -------------------------------------------------------------------------------- 1 | export * from './exception.filter' 2 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/helper/utilities.helper.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import * as moment from 'moment-timezone' 3 | moment.tz.setDefault('Asia/Karachi') 4 | 5 | import * as bcrypt from 'bcrypt' 6 | const jwt = require('jsonwebtoken') 7 | @Injectable() 8 | export class Helper { 9 | async comparePassword(plainPass, hashWord) { 10 | return await bcrypt.compare(plainPass, hashWord) 11 | } 12 | 13 | async encryptPassword(plainPass) { 14 | const saltOrRounds = 10 15 | const salt = await bcrypt.genSalt(saltOrRounds) 16 | return await bcrypt.hash(plainPass, salt) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/interceptors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './logging.interceptor' 2 | export * from './response.interceptor' 3 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/interceptors/logging.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestInterceptor, 4 | ExecutionContext, 5 | Logger, 6 | CallHandler, 7 | } from '@nestjs/common' 8 | import { Observable } from 'rxjs' 9 | import { tap } from 'rxjs/operators' 10 | 11 | @Injectable() 12 | export class LoggingInterceptor implements NestInterceptor { 13 | private logger = new Logger('HTTP') 14 | intercept(context: ExecutionContext, next: CallHandler): Observable { 15 | const ctx = context.switchToHttp() 16 | const response = ctx.getResponse() 17 | const request = ctx.getRequest() 18 | 19 | const { ip, method, originalUrl, body } = request 20 | const userAgent = request.get('user-agent') || '' 21 | 22 | response.on('finish', () => { 23 | const { statusCode } = response 24 | const contentLength = response.get('content-length') 25 | 26 | const now = Date.now() 27 | 28 | this.logger.log( 29 | `method: ${method} originalUrl:${originalUrl} statusCode:${statusCode} contentLength:${contentLength} - userAgent:${userAgent} - ip:${ip} - QueryDuration:${ 30 | Date.now() - now 31 | }ms - body:${JSON.stringify(body)}`, 32 | ) 33 | }) 34 | 35 | return next.handle().pipe(tap(() => {})) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/interceptors/response.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestInterceptor, 4 | ExecutionContext, 5 | CallHandler, 6 | } from '@nestjs/common' 7 | import { Observable } from 'rxjs' 8 | import { map } from 'rxjs/operators' 9 | import { Reflector } from '@nestjs/core' 10 | import { ResponseMessageKey } from '../decorator' 11 | 12 | export interface Response { 13 | statusCode: number 14 | message: string 15 | data: T 16 | } 17 | 18 | @Injectable() 19 | export class TransformInterceptor 20 | implements NestInterceptor> 21 | { 22 | private reflector = new Reflector() 23 | intercept( 24 | context: ExecutionContext, 25 | next: CallHandler, 26 | ): Observable> { 27 | const responseMessage = 28 | this.reflector.get(ResponseMessageKey, context.getHandler()) ?? '' 29 | return next.handle().pipe( 30 | map((data) => ({ 31 | status: 'success', 32 | statusCode: context.switchToHttp().getResponse().statusCode, 33 | reqId: context.switchToHttp().getRequest().reqId, 34 | message: responseMessage, 35 | data, 36 | })), 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/type/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mixed-array.type' 2 | export * from './sales-unit-calculation.type' 3 | export * from './send-sms.type' 4 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/type/mixed-array.type.ts: -------------------------------------------------------------------------------- 1 | export type mixedArrayType = { 2 | categories: { 3 | shops: { 4 | blocks: { 5 | blockTitle: string 6 | floors: { 7 | floorTitle: string 8 | unitNo: [] 9 | } 10 | } 11 | } 12 | apartments: { 13 | blocks: { 14 | blockTitle: string 15 | floors: { 16 | floorTitle: string 17 | unitNo: [] 18 | } 19 | } 20 | } 21 | offices: { 22 | blocks: { 23 | blockTitle: string 24 | floors: { 25 | floorTitle: string 26 | unitNo: [] 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/type/sales-unit-calculation.type.ts: -------------------------------------------------------------------------------- 1 | export type salesUnitCalculationType = { 2 | sellingPriceCash: number 3 | sellingPriceLoan: number 4 | extrasRoadFacing: number 5 | extrasWestOpen: number 6 | extrasCorner: number 7 | discount: number 8 | downPayment: number 9 | totalExtras: number 10 | grossSales: number 11 | sumOfRupees: number 12 | netSales: number 13 | recoveredAmount: number 14 | recoveryPercentage: number 15 | } 16 | -------------------------------------------------------------------------------- /01-mongoose/src/app/common/type/send-sms.type.ts: -------------------------------------------------------------------------------- 1 | export type sendSMS = { 2 | phone: string 3 | message: string 4 | } 5 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/index.ts: -------------------------------------------------------------------------------- 1 | export * from './todo/todo.module' 2 | export * from './user/user.module' 3 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/todo/dto/add-todo.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsEmail, 3 | IsNotEmpty, 4 | IsOptional, 5 | ValidateNested, 6 | } from '@nestjs/class-validator' 7 | import { ApiProperty } from '@nestjs/swagger' 8 | import { Type } from 'class-transformer' 9 | import { IsString, isNotEmpty } from 'class-validator' 10 | import { IsArrayOfObjects } from 'src/app/common/decorator' 11 | 12 | export class AddTodoDTO { 13 | @ApiProperty({ description: 'title', example: 'test' }) 14 | @IsString() 15 | @IsNotEmpty() 16 | title: string 17 | 18 | @ApiProperty({ description: 'description', example: 'test' }) 19 | @IsString() 20 | @IsNotEmpty() 21 | description: string 22 | } 23 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/todo/dto/delete-todo.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsOptional, IsString } from '@nestjs/class-validator' 2 | import { ApiProperty } from '@nestjs/swagger' 3 | import { Types } from 'mongoose' 4 | 5 | export class DeleteTodoDTO { 6 | @ApiProperty({ description: 'id', example: '6500cc53a7b91b788717134f' }) 7 | @IsNotEmpty() 8 | @IsString() 9 | id: Types.ObjectId 10 | } 11 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/todo/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from './delete-todo.dto' 2 | export * from './add-todo.dto' 3 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/todo/enum/index.ts: -------------------------------------------------------------------------------- 1 | export * from './response-enum' 2 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/todo/enum/response-enum.ts: -------------------------------------------------------------------------------- 1 | export enum responseEnum { 2 | GET_TODO = 'Todo fetched successfully.', 3 | TODO_NOT_FOUND = 'Todo not found.', 4 | TODO_ADD = 'New todo added', 5 | TODO_DELETED = 'Todo task deleted', 6 | } 7 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/todo/schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from './todo.schema' 2 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/todo/schema/todo.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose' 2 | import { Document, Schema as MongooseSchema } from 'mongoose' 3 | 4 | export type TodoDocument = Todo & Document 5 | 6 | @Schema({ 7 | strict: true, 8 | timestamps: { createdAt: true, updatedAt: true }, 9 | collection: 'todos', 10 | }) 11 | export class Todo { 12 | @Prop({ required: true }) 13 | title: string 14 | 15 | @Prop({ required: true }) 16 | description: string 17 | 18 | @Prop({ required: true }) 19 | email: string 20 | } 21 | 22 | export const TodoSchema = SchemaFactory.createForClass(Todo) 23 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/todo/todo.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | HttpCode, 6 | Post, 7 | Delete, 8 | UseGuards, 9 | UseInterceptors, 10 | } from '@nestjs/common' 11 | import { ApiCookieAuth, ApiResponse, ApiTags } from '@nestjs/swagger' 12 | import { TodoService } from './todo.service' 13 | import { responseEnum } from './enum' 14 | import { GetUser, ResponseMessage } from 'src/app/common/decorator' 15 | import { AddTodoDTO, DeleteTodoDTO } from './dto' 16 | import { JwtGuard } from 'src/app/auth/guard' 17 | import { User } from '../user/schema' 18 | import { Todo } from './schema' 19 | 20 | @Controller('todo') 21 | @ApiTags('Todo') 22 | @UseGuards(JwtGuard) 23 | @ApiCookieAuth('api-auth') 24 | export class TodoController { 25 | constructor(private readonly todoService: TodoService) {} 26 | 27 | @Get() 28 | @ResponseMessage(responseEnum.GET_TODO) 29 | @ApiResponse({ 30 | status: 200, 31 | description: responseEnum.GET_TODO, 32 | }) 33 | @ApiResponse({ 34 | status: 404, 35 | description: responseEnum.TODO_NOT_FOUND, 36 | }) 37 | @HttpCode(200) 38 | async getTodo(@GetUser() { email }: User): Promise { 39 | return await this.todoService.getTodo(email) 40 | } 41 | 42 | @Post() 43 | @ResponseMessage(responseEnum.TODO_ADD) 44 | @ApiResponse({ 45 | status: 200, 46 | description: responseEnum.TODO_ADD, 47 | }) 48 | @HttpCode(200) 49 | async setTodo( 50 | @Body() body: AddTodoDTO, 51 | @GetUser() { email }: User, 52 | ): Promise { 53 | return await this.todoService.setTodo(body, email) 54 | } 55 | 56 | @Delete() 57 | @ResponseMessage(responseEnum.TODO_DELETED) 58 | @ApiResponse({ 59 | status: 200, 60 | description: responseEnum.TODO_DELETED, 61 | }) 62 | @ApiResponse({ 63 | status: 404, 64 | description: responseEnum.TODO_NOT_FOUND, 65 | }) 66 | @HttpCode(200) 67 | async deleteUser( 68 | @GetUser() { email }: User, 69 | @Body() { id }: DeleteTodoDTO, 70 | ): Promise { 71 | return await this.todoService.deleteTodo(email, id) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/todo/todo.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { TodoController } from './todo.controller' 3 | import { TodoService } from './todo.service' 4 | import { MongooseModule } from '@nestjs/mongoose' 5 | 6 | import { connectionEnum } from 'src/app/common/enum' 7 | import { Todo, TodoSchema } from './schema' 8 | @Module({ 9 | imports: [ 10 | MongooseModule.forFeature( 11 | [{ name: Todo.name, schema: TodoSchema }], 12 | connectionEnum.database, 13 | ), 14 | ], 15 | controllers: [TodoController], 16 | providers: [TodoService], 17 | }) 18 | export class TodoModule {} 19 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/todo/todo.service.ts: -------------------------------------------------------------------------------- 1 | import { TodoDocument, Todo } from './schema' 2 | import { 3 | BadRequestException, 4 | Injectable, 5 | NotFoundException, 6 | } from '@nestjs/common' 7 | import { InjectModel } from '@nestjs/mongoose' 8 | import mongoose, { Model } from 'mongoose' 9 | import { connectionEnum } from 'src/app/common/enum' 10 | import { Types } from 'mongoose' 11 | 12 | import { responseEnum } from './enum' 13 | import { AddTodoDTO } from './dto' 14 | 15 | @Injectable() 16 | export class TodoService { 17 | constructor( 18 | @InjectModel(Todo.name, connectionEnum.database) 19 | private readonly todoModel: Model, 20 | ) {} 21 | 22 | async getTodo(email: string): Promise { 23 | const todos = await this.todoModel.find({ email }) 24 | 25 | if (!todos) throw new NotFoundException(responseEnum.TODO_NOT_FOUND) 26 | 27 | return todos 28 | } 29 | 30 | async setTodo( 31 | { title, description }: AddTodoDTO, 32 | email: string, 33 | ): Promise { 34 | await this.todoModel.create({ title, description, email }) 35 | return null 36 | } 37 | 38 | async deleteTodo(email: string, id: Types.ObjectId): Promise { 39 | const exists = await this.todoModel.exists({ email, _id: id }) 40 | 41 | if (!exists) throw new NotFoundException(responseEnum.TODO_NOT_FOUND) 42 | 43 | await this.todoModel.deleteOne({ email, _id: id }) 44 | 45 | return null 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/user/dto/delete-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsEmail, 3 | IsNotEmpty, 4 | IsOptional, 5 | ValidateNested, 6 | } from '@nestjs/class-validator' 7 | import { ApiProperty } from '@nestjs/swagger' 8 | import { Type } from 'class-transformer' 9 | import { IsArrayOfObjects } from 'src/app/common/decorator' 10 | 11 | export class DeleteUserDTO { 12 | @ApiProperty({ description: 'email', example: 'test@example.com' }) 13 | @IsEmail() 14 | @IsNotEmpty() 15 | email: string 16 | } 17 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/user/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from './update-user.dto' 2 | export * from './delete-user.dto' 3 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/user/dto/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsOptional, IsString } from '@nestjs/class-validator' 2 | import { ApiProperty } from '@nestjs/swagger' 3 | 4 | export class UpdateUserDTO { 5 | @ApiProperty({ description: 'name', example: 'test' }) 6 | @IsNotEmpty() 7 | @IsString() 8 | name: string 9 | 10 | @ApiProperty({ description: 'phone', example: '+923350000000' }) 11 | @IsOptional() 12 | @IsNotEmpty() 13 | @IsString() 14 | phone: string 15 | 16 | @ApiProperty({ description: 'address', example: 'H#abc' }) 17 | @IsOptional() 18 | @IsNotEmpty() 19 | @IsString() 20 | address: string 21 | } 22 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/user/enum/index.ts: -------------------------------------------------------------------------------- 1 | export * from './response-enum' 2 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/user/enum/response-enum.ts: -------------------------------------------------------------------------------- 1 | export enum responseEnum { 2 | GET_ALL_USERS = 'User(s) fetched successfully.', 3 | USER_DELETED = 'User deleted successfully.', 4 | USER_UPDATED = 'User updated successfully.', 5 | USER_UPDATE_FAILED = 'Failed to Update the User', 6 | USER_FOUND = 'USER fetched successfully.', 7 | USER_NOT_FOUND = 'Unable to fetch USER.', 8 | USER_NOT_FOUND_CAN_NOT_DELETED = 'User not found or you can not delete him/her', 9 | } 10 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/user/schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user.schema' 2 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/user/schema/user.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose' 2 | import { HydratedDocument } from 'mongoose' 3 | import { RoleEnum } from 'src/app/common/enum' 4 | export type UserDocument = HydratedDocument 5 | 6 | @Schema({ 7 | strict: true, 8 | timestamps: true, 9 | collection: 'users', 10 | }) 11 | export class User { 12 | @Prop({ required: true, unique: true }) 13 | email: string 14 | 15 | @Prop({ required: true }) 16 | name: string 17 | 18 | @Prop({ required: true }) 19 | phone: string 20 | 21 | @Prop({ required: true }) 22 | address: string 23 | 24 | @Prop({ required: true }) 25 | role: RoleEnum 26 | 27 | @Prop({ required: true }) 28 | password: string 29 | } 30 | 31 | export const UserSchema = SchemaFactory.createForClass(User) 32 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { User } from './schema' 2 | import { 3 | Body, 4 | Controller, 5 | Delete, 6 | Get, 7 | HttpCode, 8 | Patch, 9 | Post, 10 | Put, 11 | UseGuards, 12 | } from '@nestjs/common' 13 | import { JwtGuard, RolesGuard } from 'src/app/auth/guard' 14 | import { GetUser, ResponseMessage, Roles } from 'src/app/common/decorator' 15 | import { UsersService } from './user.service' 16 | import { ApiCookieAuth, ApiResponse, ApiTags } from '@nestjs/swagger' 17 | import { responseEnum } from './enum' 18 | import { RoleEnum } from 'src/app/common/enum' 19 | import { DeleteUserDTO, UpdateUserDTO } from './dto' 20 | @Controller('user') 21 | @ApiTags('user') 22 | @UseGuards(JwtGuard) 23 | @ApiCookieAuth('api-auth') 24 | export class UsersController { 25 | constructor(private readonly usersService: UsersService) {} 26 | 27 | @Get() 28 | @ResponseMessage(responseEnum.USER_FOUND) 29 | @ApiResponse({ 30 | status: 200, 31 | description: responseEnum.USER_FOUND, 32 | }) 33 | @ApiResponse({ 34 | status: 404, 35 | description: responseEnum.USER_NOT_FOUND, 36 | }) 37 | @HttpCode(200) 38 | async getUser(@GetUser() userData: User): Promise { 39 | return userData 40 | } 41 | 42 | @Put() 43 | @ResponseMessage(responseEnum.USER_UPDATED) 44 | @ApiResponse({ 45 | status: 200, 46 | description: responseEnum.USER_UPDATED, 47 | }) 48 | @ApiResponse({ 49 | status: 404, 50 | description: responseEnum.USER_NOT_FOUND, 51 | }) 52 | @ApiResponse({ 53 | status: 500, 54 | description: responseEnum.USER_UPDATE_FAILED, 55 | }) 56 | @HttpCode(200) 57 | async updateUser( 58 | @Body() body: UpdateUserDTO, 59 | @GetUser() { email }: User, 60 | ): Promise { 61 | return this.usersService.updateUser(body, email) 62 | } 63 | 64 | @Delete() 65 | @Roles([RoleEnum.ADMIN]) 66 | @UseGuards(RolesGuard) 67 | @ResponseMessage(responseEnum.USER_DELETED) 68 | @ApiResponse({ 69 | status: 201, 70 | description: responseEnum.USER_DELETED, 71 | }) 72 | @HttpCode(201) 73 | async deleteUser(@Body() { email }: DeleteUserDTO): Promise { 74 | return await this.usersService.deleteUser(email) 75 | } 76 | 77 | @Get('get-all-users') 78 | @Roles([RoleEnum.ADMIN]) 79 | @UseGuards(RolesGuard) 80 | @ResponseMessage(responseEnum.GET_ALL_USERS) 81 | @ApiResponse({ 82 | status: 200, 83 | description: responseEnum.GET_ALL_USERS, 84 | }) 85 | @HttpCode(200) 86 | async getAllUsers(): Promise { 87 | return await this.usersService.getAllUsers() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersController } from './user.controller' 3 | import { UsersService } from './user.service' 4 | import { MongooseModule } from '@nestjs/mongoose' 5 | 6 | import { connectionEnum } from 'src/app/common/enum' 7 | import { User, UserSchema } from './schema' 8 | 9 | @Module({ 10 | imports: [ 11 | MongooseModule.forFeature( 12 | [{ name: User.name, schema: UserSchema }], 13 | connectionEnum.database, 14 | ), 15 | ], 16 | controllers: [UsersController], 17 | providers: [UsersService], 18 | }) 19 | export class UsersModule {} 20 | -------------------------------------------------------------------------------- /01-mongoose/src/app/modules/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BadRequestException, 3 | ConflictException, 4 | Injectable, 5 | NotFoundException, 6 | } from '@nestjs/common' 7 | import { InjectModel } from '@nestjs/mongoose' 8 | import { Model } from 'mongoose' 9 | import { RoleEnum, connectionEnum } from 'src/app/common/enum' 10 | 11 | import { User, UserDocument } from 'src/app/modules/user/schema' 12 | import { responseEnum } from './enum' 13 | import { UpdateUserDTO } from './dto' 14 | 15 | @Injectable() 16 | export class UsersService { 17 | constructor( 18 | @InjectModel(User.name, connectionEnum.database) 19 | private readonly userModel: Model, 20 | ) {} 21 | 22 | async deleteUser(email: string): Promise { 23 | const exists = await this.userModel.exists({ email, role: RoleEnum.USER }) 24 | 25 | if (!exists) 26 | throw new BadRequestException(responseEnum.USER_NOT_FOUND_CAN_NOT_DELETED) 27 | 28 | await this.userModel.deleteOne({ email, role: RoleEnum.USER }) 29 | 30 | return null 31 | } 32 | 33 | async updateUser(body: UpdateUserDTO, email: string): Promise { 34 | const user = await this.userModel.findOneAndUpdate({ email }, body, { 35 | upsert: false, 36 | }) 37 | 38 | if (!user) throw new ConflictException(responseEnum.USER_UPDATE_FAILED) 39 | return null 40 | } 41 | 42 | async getAllUsers(): Promise { 43 | return this.userModel.find().select({ password: 0 }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /01-mongoose/src/config/config.default.ts: -------------------------------------------------------------------------------- 1 | import { ConfigData } from './config.interface' 2 | 3 | export const DEFAULT_CONFIG: ConfigData = { 4 | env: 'development', 5 | port: 8080, 6 | logLevel: 'info', 7 | jwtSecret: 'random string', 8 | refreshSecret: 'random string', 9 | cookieSecret: 'random string', 10 | clientDomain: 'example.com', 11 | cookieSecretExpiresDurationInMinutes: 60, 12 | transactionSecret: 'random string', 13 | cacheExpiresDurationInMinutes: 60, 14 | refreshExpiresDurationInYears: 1, 15 | clientOtpDurationInMinutes: 30, 16 | clientTransactionDurationInHours: 12, 17 | databaseURI: '', 18 | tokenExpiresDurationInMinutesForEmployee: 60, 19 | tokenExpiresDurationInMinutesForClient: 60, 20 | } 21 | -------------------------------------------------------------------------------- /01-mongoose/src/config/config.interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration data for the app. 3 | */ 4 | export interface ConfigData { 5 | env: string 6 | 7 | /** The port number of the http server to listen on. */ 8 | port: number 9 | 10 | /** 11 | * The log level to use. 12 | * @example 'verbose', 'info', 'warn', 'error' 13 | */ 14 | logLevel: string 15 | 16 | jwtSecret: string 17 | refreshSecret: string 18 | cookieSecret: string 19 | clientDomain: string 20 | cookieSecretExpiresDurationInMinutes: number 21 | transactionSecret: string 22 | 23 | /** Database connection details. */ 24 | 25 | databaseURI: string 26 | 27 | //* Duration of the token and cookie in minutes 28 | tokenExpiresDurationInMinutesForEmployee: number 29 | tokenExpiresDurationInMinutesForClient: number 30 | cacheExpiresDurationInMinutes: number 31 | refreshExpiresDurationInYears: number 32 | clientOtpDurationInMinutes: number 33 | clientTransactionDurationInHours: number 34 | } 35 | -------------------------------------------------------------------------------- /01-mongoose/src/config/config.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { ConfigService } from './config.service' 3 | 4 | const configFactory = { 5 | provide: ConfigService, 6 | useFactory: () => { 7 | const config = new ConfigService() 8 | config.lofusingDotEnv() 9 | return config 10 | }, 11 | } 12 | 13 | @Module({ 14 | imports: [], 15 | controllers: [], 16 | providers: [configFactory], 17 | exports: [configFactory], 18 | }) 19 | export class ConfigModule {} 20 | -------------------------------------------------------------------------------- /01-mongoose/src/config/config.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | import { DEFAULT_CONFIG } from './config.default' 4 | import { ConfigData } from './config.interface' 5 | 6 | /** 7 | * Provides a means to access the application configuration. 8 | */ 9 | @Injectable() 10 | export class ConfigService { 11 | private config: ConfigData 12 | 13 | constructor(data: ConfigData = DEFAULT_CONFIG) { 14 | this.config = data 15 | } 16 | 17 | /** 18 | * Loads the config from environment variables. 19 | */ 20 | public lofusingDotEnv() { 21 | this.config = this.parseConfigFromEnv(process.env) 22 | } 23 | 24 | private parseConfigFromEnv(env: NodeJS.ProcessEnv): ConfigData { 25 | return { 26 | env: env.NODE_ENV || DEFAULT_CONFIG.env, 27 | port: Number(env.PORT) || DEFAULT_CONFIG.port, 28 | logLevel: env.LOG_LEVEL || DEFAULT_CONFIG.logLevel, 29 | jwtSecret: env.JWT_SECRET || DEFAULT_CONFIG.jwtSecret, 30 | refreshSecret: env.REFRESH_SECRET || DEFAULT_CONFIG.refreshSecret, 31 | cookieSecret: env.COOKIE_SECRET || DEFAULT_CONFIG.cookieSecret, 32 | clientDomain: env.CLIENT_DOMAIN || DEFAULT_CONFIG.clientDomain, 33 | cookieSecretExpiresDurationInMinutes: 34 | Number(env.CACHE_EXPIRES_DURATION_IN_MINUTES) || 35 | DEFAULT_CONFIG.cookieSecretExpiresDurationInMinutes, 36 | transactionSecret: 37 | env.TRANSACTION_SECRET || DEFAULT_CONFIG.transactionSecret, 38 | databaseURI: env.DATABASE_URI || DEFAULT_CONFIG.databaseURI, 39 | tokenExpiresDurationInMinutesForEmployee: 40 | Number(env.TOKEN_EXPIRES_DURATION_IN_MINUTES) || 41 | DEFAULT_CONFIG.tokenExpiresDurationInMinutesForEmployee, 42 | tokenExpiresDurationInMinutesForClient: 43 | Number(env.TOKEN_EXPIRES_DURATION_IN_MINUTES) || 44 | DEFAULT_CONFIG.tokenExpiresDurationInMinutesForClient, 45 | cacheExpiresDurationInMinutes: 46 | Number(env.CACHE_EXPIRES_DURATION_IN_MINUTES) || 47 | DEFAULT_CONFIG.cacheExpiresDurationInMinutes, 48 | refreshExpiresDurationInYears: 49 | Number(env.REFRESH_EXPIRES_DURATION_IN_YEARS) || 50 | DEFAULT_CONFIG.refreshExpiresDurationInYears, 51 | clientOtpDurationInMinutes: 52 | Number(env.CLIENT_OTP_VALIDITY_DURATION_IN_MINUTES) || 53 | DEFAULT_CONFIG.clientOtpDurationInMinutes, 54 | clientTransactionDurationInHours: 55 | Number(env.CLIENT_TRANSACTION_VALIDITY_DURATION_IN_HOURS) || 56 | DEFAULT_CONFIG.clientTransactionDurationInHours, 57 | } 58 | } 59 | public get(): Readonly { 60 | return this.config 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /01-mongoose/src/database/database.module.ts: -------------------------------------------------------------------------------- 1 | import { ConfigService } from 'src/config/config.service' 2 | import { Module, DynamicModule } from '@nestjs/common' 3 | import { ConfigModule } from 'src/config/config.module' 4 | import { DbConfigError, DbError } from './db.error' 5 | import { MongooseModule, MongooseModuleOptions } from '@nestjs/mongoose' 6 | import { connectionEnum } from 'src/app/common/enum' 7 | 8 | @Module({}) 9 | export class DatabaseModule { 10 | public static getNoSqlConnectionOptions( 11 | config: ConfigService, 12 | databaseName: string, 13 | ): MongooseModuleOptions { 14 | const databaseURI = config.get()[databaseName] 15 | 16 | if (!databaseURI) { 17 | throw new DbConfigError('Database config is missing') 18 | } 19 | return { 20 | uri: databaseURI, 21 | } 22 | } 23 | public static forRoot(): DynamicModule { 24 | return { 25 | module: DatabaseModule, 26 | imports: [ 27 | MongooseModule.forRootAsync({ 28 | imports: [ConfigModule], 29 | useFactory: (configService: ConfigService) => 30 | DatabaseModule.getNoSqlConnectionOptions( 31 | configService, 32 | 'databaseURI', 33 | ), 34 | inject: [ConfigService], 35 | connectionName: connectionEnum.database, 36 | }), 37 | ], 38 | controllers: [], 39 | providers: [], 40 | exports: [], 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /01-mongoose/src/database/db.error.ts: -------------------------------------------------------------------------------- 1 | export class DbError extends Error { 2 | public constructor(message = 'Unknown database error') { 3 | super(message) 4 | } 5 | } 6 | 7 | // tslint:disable-next-line: max-classes-per-file 8 | export class DbConfigError extends DbError { 9 | public constructor(message = 'Database configuration error') { 10 | super(message) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /01-mongoose/src/logger/logger.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware } from '@nestjs/common' 2 | import { Request, Response } from 'express' 3 | import * as moment from 'moment-timezone' 4 | moment.tz.setDefault('Asia/Karachi') 5 | 6 | import { Logger } from './logger' 7 | 8 | @Injectable() 9 | export class LoggerMiddleware implements NestMiddleware { 10 | public constructor(private logger: Logger) {} 11 | 12 | public use(req: Request, res: Response, next: () => void): any { 13 | const before = Date.now() 14 | next() 15 | res.on('close', () => 16 | this.logger.http(this.generateLogMessage(req, res, Date.now() - before)), 17 | ) 18 | } 19 | 20 | /* 21 | - COMBINED LOG FORMAT - 22 | %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" 23 | 24 | %h: ip 25 | %l: client identd (leave as '-') 26 | %u: HTTP auth userid (can leave as '-') 27 | %t: time (e.g. '[10/Oct/2000:13:55:36 -0700]') 28 | %r: request line (e.g. 'GET /apache_pb.gif HTTP/1.0') 29 | %>s: HTTP status code 30 | %b: response size in bytes (leave as '-' if 0) 31 | %{Referer}i: "Referer" HTTP request header 32 | %{User-agent}i: "User-Agent" HTTP request header 33 | */ 34 | 35 | private getResponseSize(res: Response): number { 36 | const sizeRaw = res.getHeader('Content-Length') 37 | if (typeof sizeRaw === 'number') { 38 | return sizeRaw 39 | } 40 | if (typeof sizeRaw === 'string') { 41 | const parsed = parseInt(sizeRaw, 10) 42 | if (isNaN(parsed)) { 43 | return 0 44 | } 45 | return parsed 46 | } 47 | return 0 48 | } 49 | 50 | private generateLogMessage( 51 | req: Request, 52 | res: Response, 53 | timeTaken: number, 54 | ): string { 55 | const size = this.getResponseSize(res) 56 | const terms: { [key: string]: string } = { 57 | '%h': req.socket.remoteAddress || '-', 58 | '%l': '-', 59 | '%u': '-', // todo: parse req.headers.authorization? 60 | '%t': `[${moment().format('DD/MMM/YYYY:HH:mm:ss ZZ')}]`, 61 | '%r': `${req.method} ${req.originalUrl} ${req.httpVersion}`, 62 | '%>s': `${res.statusCode}`, 63 | '%b': size === 0 ? '-' : `${size}`, 64 | } 65 | let str = '%h %l %u %t "%r" %>s %b %{Referer}i %{User-agent}i' 66 | for (const term in terms) { 67 | if (term in terms) { 68 | str = str.replace(term, terms[term]) 69 | } 70 | } 71 | str = str.replace(/%\{([a-zA-Z\-]+)\}i/g, (match, p1) => { 72 | const header = req.headers[`${p1}`.toLowerCase()] 73 | if (header == null) { 74 | return '-' 75 | } 76 | if (Array.isArray(header)) { 77 | return `"${header.join(',')}"` 78 | } 79 | return `"${header}"` 80 | }) 81 | return str 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /01-mongoose/src/logger/logger.module.ts: -------------------------------------------------------------------------------- 1 | import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common' 2 | 3 | import { ConfigModule } from '../config/config.module' 4 | import { Logger } from './logger' 5 | import { LoggerMiddleware } from './logger.middleware' 6 | 7 | @Module({ 8 | imports: [ConfigModule], 9 | controllers: [], 10 | providers: [Logger], 11 | exports: [Logger], 12 | }) 13 | export class LoggerModule implements NestModule { 14 | public configure(consumer: MiddlewareConsumer) { 15 | consumer.apply(LoggerMiddleware).forRoutes('*') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /01-mongoose/src/logger/logger.spec.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai' 2 | import * as spies from 'chai-spies' 3 | 4 | import { DEFAULT_CONFIG } from '../../config/config.default' 5 | import { ConfigService } from '../../config/config.service' 6 | import { Logger } from './logger' 7 | import { LogLevel } from './loglevel' 8 | 9 | chai.use(spies) 10 | 11 | const TEST_MESSAGE = 'test message' 12 | 13 | describe('Logger', () => { 14 | let logger: Logger 15 | 16 | const logLevels = [ 17 | LogLevel.Error, 18 | LogLevel.Warn, 19 | LogLevel.Info, 20 | LogLevel.HTTP, 21 | LogLevel.Verbose, 22 | LogLevel.Debug, 23 | LogLevel.Silly, 24 | ] 25 | 26 | /* tslint:disable-next-line:no-empty */ 27 | const noop = () => {} 28 | 29 | describe('constructor', () => { 30 | it('should create winston logger with correct log level', () => { 31 | for (const logLevel of logLevels) { 32 | const configService = new ConfigService({ 33 | ...DEFAULT_CONFIG, 34 | logLevel, 35 | }) 36 | logger = new Logger(configService) 37 | chai.expect(logger.logger.level).to.equal(logLevel) 38 | } 39 | }) 40 | }) 41 | 42 | describe('log', () => { 43 | it('should pass the message to winston logger, with log level overload', () => { 44 | const configService = new ConfigService() 45 | for (const logLevel of logLevels) { 46 | logger = new Logger(configService) 47 | const logSpy = chai.spy.on(logger.logger, 'log', noop) 48 | logger.log(logLevel, TEST_MESSAGE) 49 | chai 50 | .expect(logSpy) 51 | .to.have.been.called.with.exactly(logLevel, TEST_MESSAGE) 52 | } 53 | }) 54 | 55 | it('should pass the message to winston logger, without log level overload', () => { 56 | const configService = new ConfigService() 57 | logger = new Logger(configService) 58 | const logSpy = chai.spy.on(logger.logger, 'log', noop) 59 | logger.log(TEST_MESSAGE) 60 | chai 61 | .expect(logSpy) 62 | .to.have.been.called.with.exactly(LogLevel.Info, TEST_MESSAGE) 63 | }) 64 | }) 65 | 66 | for (const logLevel of logLevels) { 67 | describe(logLevel, () => { 68 | it('should pass the message to winston logger', () => { 69 | const configService = new ConfigService() 70 | logger = new Logger(configService) 71 | const logSpy = chai.spy.on(logger.logger, 'log', noop) 72 | logger[logLevel](TEST_MESSAGE) 73 | chai 74 | .expect(logSpy) 75 | .to.have.been.called.with.exactly(logLevel, TEST_MESSAGE) 76 | }) 77 | }) 78 | } 79 | }) 80 | -------------------------------------------------------------------------------- /01-mongoose/src/logger/logger.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, LoggerService } from '@nestjs/common' 2 | import * as moment from 'moment-timezone' 3 | moment.tz.setDefault('Asia/Karachi') 4 | import { MESSAGE } from 'triple-beam' 5 | import * as winston from 'winston' 6 | 7 | import { ConfigService } from '../config/config.service' 8 | import { isLogLevel, LogLevel } from './loglevel' 9 | 10 | const formatter = winston.format((info) => { 11 | if (info.level === LogLevel.HTTP) { 12 | // HTTP messages are already formatted by the middleware, so just pass through 13 | return info 14 | } 15 | info.message = `[${moment().format('ddd MMM DD HH:mm:ss YYYY')}] [${ 16 | info.level 17 | }] ${info.message}` 18 | return info 19 | }) 20 | 21 | const passthrough = winston.format((info) => { 22 | info[MESSAGE] = info.message 23 | return info.message 24 | }) 25 | 26 | /** 27 | * Provides a means to write log messages. 28 | */ 29 | @Injectable() 30 | export class Logger implements LoggerService { 31 | private logger: winston.Logger 32 | 33 | constructor(private configService: ConfigService) { 34 | this.logger = winston.createLogger({ 35 | level: configService.get().logLevel, 36 | format: formatter(), 37 | }) 38 | this.logger.add( 39 | new winston.transports.Console({ 40 | format: passthrough(), 41 | stderrLevels: [LogLevel.Error, LogLevel.Warn], 42 | }), 43 | ) 44 | } 45 | 46 | /** 47 | * Writes a log message. 48 | * @param level the severity of the message 49 | * @param message the log message 50 | */ 51 | public log(level: LogLevel, message: string): void 52 | /** 53 | * Writes a log message with the {@link LogLevel.Info} log level. 54 | * @param message the log message 55 | */ 56 | public log(message: string): void 57 | public log(p0: LogLevel | string, p1?: string) { 58 | const logLevel = isLogLevel(p0) ? p0 : LogLevel.Info 59 | const message = isLogLevel(p0) && p1 ? p1 : p0 60 | this.logger.log(logLevel, message) 61 | } 62 | 63 | /** 64 | * Writes a log message with the {@link LogLevel.Error} log level. 65 | * @param message the log message 66 | */ 67 | public error(message: string) { 68 | this.log(LogLevel.Error, message) 69 | } 70 | 71 | /** 72 | * Writes a log message with the {@link LogLevel.Warn} log level. 73 | * @param message the log message 74 | */ 75 | public warn(message: string) { 76 | this.log(LogLevel.Warn, message) 77 | } 78 | 79 | /** 80 | * Writes a log message with the {@link LogLevel.Info} log level. 81 | * @param message the log message 82 | */ 83 | public info(message: string) { 84 | this.log(LogLevel.Info, message) 85 | } 86 | 87 | /** 88 | * Writes a log message with the {@link LogLevel.HTTP} log level. 89 | * @param message the log message 90 | */ 91 | public http(message: string) { 92 | this.log(LogLevel.HTTP, message) 93 | } 94 | 95 | /** 96 | * Writes a log message with the {@link LogLevel.Verbose} log level. 97 | * @param message the log message 98 | */ 99 | public verbose(message: string) { 100 | this.log(LogLevel.Verbose, message) 101 | } 102 | 103 | /** 104 | * Writes a log message with the {@link LogLevel.Debug} log level. 105 | * @param message the log message 106 | */ 107 | public debug(message: string) { 108 | this.log(LogLevel.Debug, message) 109 | } 110 | 111 | /** 112 | * Writes a log message with the {@link LogLevel.Silly} log level. 113 | * @param message the log message 114 | */ 115 | public silly(message: string) { 116 | this.log(LogLevel.Silly, message) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /01-mongoose/src/logger/loglevel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Indicates the severity of a log message. 3 | */ 4 | export enum LogLevel { 5 | /** Critical error, system stability is affected. */ 6 | Error = 'error', 7 | 8 | /** Non-critical error, system stability is not affected, but issue should be investigated. */ 9 | Warn = 'warn', 10 | 11 | /** Informative message. */ 12 | Info = 'info', 13 | 14 | /** HTTP access logging. */ 15 | HTTP = 'http', 16 | 17 | /** More verbose informative message. */ 18 | Verbose = 'verbose', 19 | 20 | /** Message to assist with debugging. */ 21 | Debug = 'debug', 22 | 23 | /** Unnecessarily noisy or frequent message. */ 24 | Silly = 'silly', 25 | } 26 | 27 | const allLogLevels: string[] = [ 28 | LogLevel.Error, 29 | LogLevel.Warn, 30 | LogLevel.Info, 31 | LogLevel.HTTP, 32 | LogLevel.Verbose, 33 | LogLevel.Debug, 34 | LogLevel.Silly, 35 | ] 36 | 37 | /** 38 | * Determines if the value is a valid log level or not. 39 | * @param value the value to test 40 | * @returns true if a log level, false if not 41 | */ 42 | export function isLogLevel(value: unknown): value is LogLevel { 43 | if (typeof value !== 'string') { 44 | return false 45 | } 46 | return allLogLevels.indexOf(value) !== -1 47 | } 48 | -------------------------------------------------------------------------------- /01-mongoose/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Logger, ValidationPipe, VersioningType } from '@nestjs/common' 2 | import { NestFactory } from '@nestjs/core' 3 | import { ConfigService } from './config/config.service' 4 | import { AppModule } from './app.module' 5 | 6 | import { HttpExceptionFilter } from './app/common/filter/exception.filter' 7 | import { createDocument } from './swagger/swagger' 8 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger' 9 | import * as dotenv from 'dotenv' 10 | import helmet from 'helmet' 11 | import * as cookieParser from 'cookie-parser' 12 | 13 | // import * as admin from 'firebase-admin'; 14 | // const serviceAccount = require('./firebase-adminSDK.json'); 15 | 16 | dotenv.config() 17 | import 'reflect-metadata' 18 | import { 19 | LoggingInterceptor, 20 | TransformInterceptor, 21 | } from './app/common/interceptors' 22 | 23 | const logger = new Logger('main') 24 | ;(async () => { 25 | const app = await NestFactory.create(AppModule, { 26 | bufferLogs: true, 27 | cors: true, 28 | }) 29 | 30 | // app.enableCors({ 31 | // allowedHeaders: 32 | // 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Observe', 33 | // origin: true, 34 | // methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', 35 | // }); 36 | 37 | // set helmet to protect from well-known web vulnerabilities by setting HTTP headers appropriately. 38 | app.use(helmet()) 39 | 40 | // set validation pipe to validate request body 41 | app.useGlobalPipes( 42 | new ValidationPipe({ 43 | whitelist: true, 44 | forbidNonWhitelisted: true, 45 | forbidUnknownValues: true, 46 | }), 47 | ) 48 | // set exception filter to handle all exceptions 49 | app.useGlobalFilters(new HttpExceptionFilter()) 50 | 51 | // set versioning to all routes 52 | app.enableVersioning({ 53 | type: VersioningType.URI, 54 | defaultVersion: '1', 55 | }) 56 | 57 | // set logging interceptor to log all requests 58 | app.useGlobalInterceptors(new LoggingInterceptor()) 59 | 60 | // set response transform interceptor to log all requests 61 | app.useGlobalInterceptors(new TransformInterceptor()) 62 | 63 | const configService = app.get(ConfigService) 64 | 65 | const config = new DocumentBuilder() 66 | .setTitle('Mongoose Application') 67 | .setDescription('Mongoose API Application') 68 | .setVersion('v1') 69 | .addTag('Development') 70 | .addCookieAuth( 71 | 'api-auth', // This name here is important for matching up with @ApiBearerAuth() in your controller! 72 | { 73 | type: 'http', 74 | scheme: 'bearer', 75 | in: 'header', 76 | }, 77 | ) 78 | .build() 79 | const document = SwaggerModule.createDocument(app, config) 80 | 81 | SwaggerModule.setup('/v1/swagger', app, document) 82 | 83 | app.use(cookieParser(configService.get().cookieSecret)) 84 | 85 | await app.listen(configService.get().port || 4000) 86 | logger.log(`SERVER IS RUNNING ON PORT ${configService.get().port}`) 87 | })() 88 | -------------------------------------------------------------------------------- /01-mongoose/src/swagger/swagger.config.ts: -------------------------------------------------------------------------------- 1 | import { SwaggerConfig } from './swagger.interface' 2 | 3 | /** 4 | * Configuration for the swagger UI (found at /api). 5 | * Change this to suit your app! 6 | */ 7 | export const SWAGGER_CONFIG: SwaggerConfig = { 8 | title: '01-mongoose', 9 | description: 'Template', 10 | version: '1.0', 11 | tags: ['Template'], 12 | } 13 | -------------------------------------------------------------------------------- /01-mongoose/src/swagger/swagger.interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Specifies configuration for the swagger UI (found at /api). 3 | */ 4 | export interface SwaggerConfig { 5 | title: string 6 | description: string 7 | version: string 8 | tags: string[] 9 | } 10 | -------------------------------------------------------------------------------- /01-mongoose/src/swagger/swagger.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger' 3 | 4 | import { SWAGGER_CONFIG } from './swagger.config' 5 | 6 | /** 7 | * Creates an OpenAPI document for an application, via swagger. 8 | * @param app the nestjs application 9 | * @returns the OpenAPI document 10 | */ 11 | export function createDocument(app: INestApplication): OpenAPIObject { 12 | const builder = new DocumentBuilder() 13 | .setTitle(SWAGGER_CONFIG.title) 14 | .setDescription(SWAGGER_CONFIG.description) 15 | .addBearerAuth( 16 | { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, 17 | 'access-token', 18 | ) 19 | .setVersion(SWAGGER_CONFIG.version) 20 | for (const tag of SWAGGER_CONFIG.tags) { 21 | builder.addTag(tag) 22 | } 23 | const options = builder.build() 24 | 25 | return SwaggerModule.createDocument(app, options) 26 | } 27 | -------------------------------------------------------------------------------- /01-mongoose/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import { INestApplication } from '@nestjs/common' 3 | import * as request from 'supertest' 4 | import { AppModule } from '../src/app.module' 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile() 13 | 14 | app = moduleFixture.createNestApplication() 15 | await app.init() 16 | }) 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!') 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /01-mongoose/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /01-mongoose/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /01-mongoose/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "noImplicitAny": false, 16 | "strict": false, 17 | "strictNullChecks": false, 18 | "strictBindCallApply": false, 19 | "forceConsistentCasingInFileNames": false, 20 | "noFallthroughCasesInSwitch": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 15 | do not have permission to do that, you may request the second reviewer to merge it for you. 16 | 17 | ## Code of Conduct 18 | 19 | ### Our Pledge 20 | 21 | In the interest of fostering an open and welcoming environment, we as 22 | contributors and maintainers pledge to making participation in our project and 23 | our community a harassment-free experience for everyone, regardless of age, body 24 | size, disability, ethnicity, gender identity and expression, level of experience, 25 | nationality, personal appearance, race, religion, or sexual identity and 26 | orientation. 27 | 28 | ### Our Standards 29 | 30 | Examples of behavior that contributes to creating a positive environment 31 | include: 32 | 33 | * Using welcoming and inclusive language 34 | * Being respectful of differing viewpoints and experiences 35 | * Gracefully accepting constructive criticism 36 | * Focusing on what is best for the community 37 | * Showing empathy towards other community members 38 | 39 | Examples of unacceptable behavior by participants include: 40 | 41 | * The use of sexualized language or imagery and unwelcome sexual attention or 42 | advances 43 | * Trolling, insulting/derogatory comments, and personal or political attacks 44 | * Public or private harassment 45 | * Publishing others' private information, such as a physical or electronic 46 | address, without explicit permission 47 | * Other conduct which could reasonably be considered inappropriate in a 48 | professional setting 49 | 50 | ### Our Responsibilities 51 | 52 | Project maintainers are responsible for clarifying the standards of acceptable 53 | behavior and are expected to take appropriate and fair corrective action in 54 | response to any instances of unacceptable behavior. 55 | 56 | Project maintainers have the right and responsibility to remove, edit, or 57 | reject comments, commits, code, wiki edits, issues, and other contributions 58 | that are not aligned to this Code of Conduct, or to ban temporarily or 59 | permanently any contributor for other behaviors that they deem inappropriate, 60 | threatening, offensive, or harmful. 61 | 62 | ### Scope 63 | 64 | This Code of Conduct applies both within project spaces and in public spaces 65 | when an individual is representing the project or its community. Examples of 66 | representing a project or community include using an official project e-mail 67 | address, posting via an official social media account, or acting as an appointed 68 | representative at an online or offline event. Representation of a project may be 69 | further defined and clarified by project maintainers. 70 | 71 | ### Enforcement 72 | 73 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 74 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 75 | complaints will be reviewed and investigated and will result in a response that 76 | is deemed necessary and appropriate to the circumstances. The project team is 77 | obligated to maintain confidentiality with regard to the reporter of an incident. 78 | Further details of specific enforcement policies may be posted separately. 79 | 80 | Project maintainers who do not follow or enforce the Code of Conduct in good 81 | faith may face temporary or permanent repercussions as determined by other 82 | members of the project's leadership. 83 | 84 | ### Attribution 85 | 86 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 87 | available at [http://contributor-covenant.org/version/1/4][version] 88 | 89 | [homepage]: http://contributor-covenant.org 90 | [version]: http://contributor-covenant.org/version/1/4/ 91 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Asharib Ahmed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

NestJS Boilerplates

2 | 3 | ![1500x500](https://github.com/CodeAshing/nestjs-boilerplates/assets/51862545/94cbd7f1-dc7e-4e58-98b0-fec8df0127cd) 4 | 5 | Welcome to the Nest Boilerplates project! This repository aims to provide a collection of boilerplates for Nest based projects, catering to various use cases and scenarios. Whether you are starting a new project or need a starting point for refactoring an existing app, these boilerplates will help you get started quickly with enterprise-level, scalable applications. 6 | 7 | ## Table of Contents 8 | 1. [Introduction](#introduction) 9 | 2. [Getting Started](#getting-started) 10 | 3. [Contribution Guidelines](#contribution-guidelines) 11 | 4. [Project Boilerplates](#project-boilerplates) 12 | - [01-mongoose-base](#01-mongoose-base) 13 | - [02-nuxtjs](#02-nuxtjs) (Coming Soon) 14 | - [03-sequelize](#03-sequelize) (Coming Soon) 15 | - [04-mongodb-typeorm](#04-mongodb-typeorm) (Coming Soon) 16 | - [05-webpack](#05-webpack) (Coming Soon) 17 | - [06-babel-example](#06-babel-example) (Coming Soon) 18 | - [07-fastify](#07-fastify) (Coming Soon) 19 | - [08-swagger](#08-swagger) (Coming Soon) 20 | - [09-graphql-schema-first](#09-graphql-schema-first) (Coming Soon) 21 | - [10-graphql-federation-schema-first](#10-graphql-federation-schema-first) (Coming Soon) 22 | - [11-graphql-mercurius](#11-graphql-mercurius) (Coming Soon) 23 | - [12-mvc](#12-mvc) (Coming Soon) 24 | - [13-gateways-ws](#13-gateways-ws) (Coming Soon) 25 | - [14-mvc-fastify](#14-mvc-fastify) (Coming Soon) 26 | - [15-context](#15-context) (Coming Soon) 27 | - [16-auth-jwt](#16-auth-jwt) (Coming Soon) 28 | - [17-cache](#17-cache) (Coming Soon) 29 | - [18-serializer](#18-serializer) (Coming Soon) 30 | - [19-graphql-prisma](#19-graphql-prisma) (Coming Soon) 31 | - [20-graphql-code-first](#20-graphql-code-first) (Coming Soon) 32 | - [21-serve-static](#21-serve-static) (Coming Soon) 33 | - [22-dynamic-modules](#22-dynamic-modules) (Coming Soon) 34 | - [23-queues](#23-queues) (Coming Soon) 35 | - [24-scheduling](#24-scheduling) (Coming Soon) 36 | - [25-sse](#25-sse) (Coming Soon) 37 | - [26-file-upload](#26-file-upload) (Coming Soon) 38 | - [27-event-emitter](#27-event-emitter) (Coming Soon) 39 | - [28-graphql-federation-code-first](#28-graphql-federation-code-first) (Coming Soon) 40 | 41 | 42 | 43 | # Introduction 44 | 45 | The Nest Boilerplates project is an open-source initiative to provide pre-configured boilerplates for developers building applications on the Nest framework. We understand the time-consuming process of starting a project from scratch and setting up all the necessary components. This project aims to improve your development experience by offering boilerplates for various scenarios. 46 | 47 | Each folder in this repository represents a different boilerplate project that focuses on a particular usage or integration, such as Mongoose, Sequelize, GraphQL, caching, authentication, and more. With detailed explanations and step-by-step guides, you can get started with your preferred boilerplate in just a matter of minutes! 48 | 49 | To further simplify the process, we also have plans to release a CLI tool that allows you to create a project based on any of these boilerplates with a simple command. 50 | 51 | We welcome you to be a part of this exciting journey to enhance the Nest ecosystem and create a better development experience for everyone. 52 | 53 | # Getting Started 54 | 55 | To get started with the Nest Boilerplates project, firstly, clone this repository to your local development environment: 56 | 57 | ```bash 58 | git clone https://github.com/CodeAshing/nestjs-boilerplates.git 59 | ``` 60 | 61 | Then, navigate to the cloned repository: 62 | 63 | ```bash 64 | cd nest-boilerplates 65 | ``` 66 | 67 | Here, you will find the `contributing.md` file that outlines the contribution guidelines for this project. Please make sure to read and follow these guidelines before submitting any pull requests. 68 | 69 | ## Contribution Guidelines 70 | 71 | Contributions to the Nest Boilerplates project are highly encouraged and appreciated. To help us maintain a high level of quality, please follow these contribution guidelines: 72 | 73 | 1. Fork the repository to your GitHub account. 74 | 2. Make changes and improvements in your forked repository. 75 | 3. Test your changes thoroughly. 76 | 4. Create a well-explained pull request detailing the changes you made and the problems they solve. 77 | 5. Ensure your pull requests comply with the coding standards and styles followed in the project. 78 | 6. Be responsive to feedback and iterate on your contributions, if necessary. 79 | 80 | Upon review, your pull request will go through a validation process by the project maintainers. Once approved, your changes will be merged into the main branch, and you will become a contributor to the Nest Boilerplates project. We appreciate your efforts in making this project better and more valuable. 81 | 82 | Please follow the contribution guidelines outlined in the [Contributing.md](contributing.md) file in this repository, to make sure that your contributions align with the project standards. 83 | 84 | ## Project Boilerplates 85 | 86 | Listed below are the currently available project boilerplates with their respective explanations and key features. Click on the arrow icon (▼) to expand/collapse each section. 87 | 88 |
89 | 01-mongoose-base 90 | 91 | - **Description**: Base setup for a Nest application using Mongoose as the ORM and MongoDB as the database. 92 | - **Features**: Project structure, basic MongoDB and Mongoose configurations. 93 |
94 | 95 |
96 | 02-nuxtjs (Coming Soon) 97 | 98 | - **Description**: An example boilerplate for integrating Nuxt.js into a Nest application (Coming Soon). 99 | - **Features**: Nuxt.js integration and relevant code examples (Coming Soon). 100 |
101 | 102 |
103 | 03-sequelize (Coming Soon) 104 | 105 | - **Description**: Provides a boilerplate for Nest projects using Sequelize, an ORM (Object-Relational Mapping) library for SQL-based databases (Coming Soon). 106 | - **Features**: Includes SequelizeModule and relevant code for common database operations (Coming Soon). 107 |
108 | 109 |
110 | 04-mongodb-typeorm (Coming Soon) 111 | 112 | - **Description**: A boilerplate with Nest and MongoDB integration using TypeORM, an ORM with TypeScript support (Coming Soon). 113 | - **Features**: TypeORM entities, migrations, repositories, and integration testing setup (Coming Soon). 114 |
115 | 116 |
117 | 05-webpack (Coming Soon) 118 | 119 | - **Description**: Boilerplate showcasing the integration of Nest with Webpack, a popular bundling tool (Coming Soon). 120 | - **Features**: Webpack setup and configuration for building frontend assets (Coming Soon). 121 |
122 | 123 |
124 | 06-babel-example (Coming Soon) 125 | 126 | - **Description**: An example boilerplate to demonstrate Nest application setup using Babel, a JavaScript compiler (Coming Soon). 127 | - **Features**: Babel configuration, decorators usage, and Transpile-Time Metadata support (Coming Soon). 128 |
129 | 130 |
131 | 07-fastify (Coming Soon) 132 | 133 | - **Description**: A Nest boilerplate with Fastify HTTP server (Coming Soon). 134 | - **Features**: Fastify integration with Nest and relevant code examples (Coming Soon). 135 |
136 | 137 |
138 | 08-swagger (Coming Soon) 139 | 140 | - **Description**: Provides a boilerplate with Swagger OpenAPI documentation for your Nest application (Coming Soon). 141 | - **Features**: Integration with Swagger, automated API documentation for easy development and testing (Coming Soon). 142 |
143 | 144 |
145 | 09-graphql-schema-first (Coming Soon) 146 | - **Description**: A Nest boilerplate showcasing how to use GraphQL schema-first approach (Coming Soon). 147 | - **Features**: Integration of GraphQL using the Schema-First approach and relevant code generation (Coming Soon). 148 |
149 | 150 |
151 | 10-graphql-federation-schema-first (Coming Soon) 152 | 153 | - **Description**: Demonstration of GraphQL schema-first approach for implementing the GraphQL Federation protocol in Nest (Coming Soon). 154 | - **Features**: Federated GraphQL setup and relevant code (Coming Soon). 155 |
156 | 157 |
158 | 11-graphql-mercurius (Coming Soon) 159 | 160 | - **Description**: Project template illustrating how to integrate the Mercurius GraphQL framework into your Nest application (Coming Soon). 161 | - **Features**: Mercurius integration, queries, mutations, subscriptions, and examples showcasing similar functionality as the Apollo Server integration (Coming Soon). 162 |
163 | 164 |
165 | 12-mvc (Coming Soon) 166 | 167 | - **Description**: An MVC (Model-View-Controller) structured Nest boilerplate (Coming Soon). 168 | - **Features**: Fit with a project structure reflecting an MVC pattern (Coming Soon). 169 |
170 | 171 |
172 | 13-gateways-ws (Coming Soon) 173 | 174 | - **Description**: Boilerplate with WebSocket Gateway implementation using Nest (Coming Soon). 175 | - **Features**: WebSocket Gateway, bidirectional communication, and relevant code (Coming Soon). 176 |
177 | 178 |
179 | 14-mvc-fastify (Coming Soon) 180 | 181 | - **Description**: Another MVC-based Nest boilerplate that incorporates Fastify for handling HTTP requests (Coming Soon). 182 | - **Features**: Project structure, controller, service, and module implementation, routing example, Fastify integration example (Coming Soon). 183 |
184 | 185 |
186 | 15-context (Coming Soon) 187 | 188 | - **Description**: A Nest boilerplate demonstrating the use cases of custom request context creation (Coming Soon). 189 | - **Features**: Custom request scope, injector chaining, and async context usage examples (Coming Soon). 190 |
191 | 192 |
193 | 16-auth-jwt (Coming Soon) 194 | 195 | - **Description**: A boilerplate showcasing JWT (JSON Web Token) authentication implementation in a Nest application (Coming Soon). 196 | - **Features**: User registration, token generation, authentication middleware setup, and example usage (Coming Soon). 197 |
198 | 199 |
200 | 17-cache (Coming Soon) 201 | 202 | - **Description**: Provides a boilerplate showcasing how to integrate and use cache mechanisms in Nest projects (Coming Soon). 203 | - **Features**: Memory-based caching example using the CacheManager package and built-in decorators (Coming Soon). 204 |
205 | 206 |
207 | 18-serializer (Coming Soon) 208 | 209 | - **Description**: Nest project template demonstrating serialization and serialization tuning techniques (Coming Soon). 210 | - **Features**: Serialize entity relationships, transform responses, apply groups for data transformation, including customized JSON output examples (Coming Soon). 211 |
212 | 213 |
214 | 19-graphql-prisma (Coming Soon) 215 | 216 | - **Description**: Boilerplate project demonstrating how to integrate Prisma2 with Nest applications for advanced data manipulation using GraphQL (Coming Soon). 217 | - **Features**: Prisma2 setup, GraphQL queries, migrations, batching, nested writes, and more (Coming Soon). 218 |
219 | 220 |
221 | 20-graphql-code-first (Coming Soon) 222 | 223 | - **Description**: An example boilerplate for building Nest applications using a Code-First approach with GraphQL (Coming Soon). 224 | - **Features**: TypeGraphQL integration, schema-first resolvers, example queries and mutations, authentication middleware (Coming Soon). 225 |
226 | 227 |
228 | 21-serve-static (Coming Soon) 229 | 230 | - **Description**: Nest boilerplate to serve static HTML files using built-in Express module (Coming Soon). 231 | - **Features**: Serve static files configuration, example implementation (Coming Soon). 232 |
233 | 234 |
235 | 22-dynamic-modules (Coming Soon) 236 | 237 | - **Description**: Boilerplate showcasing how to dynamically load modules in Nest applications (Coming Soon). 238 | - **Features**: Dynamic module usage example, on-demand module registration, configuration (Coming Soon). 239 |
240 | 241 |
242 | 23-queues (Coming Soon) 243 | 244 | - **Description**: Nest boilerplate with an example of using queues (Redis, Bull, Nest) for processing data asynchronously (Coming Soon). 245 | - **Features**: Queue module and worker setup (Coming Soon). 246 |
247 | 248 |
249 | 24-scheduling (Coming Soon) 250 | 251 | - **Description**: Provides a boilerplate for scheduling periodic tasks in a Nest application (Coming Soon). 252 | - **Features**: Setup and configuration of scheduled tasks using Nest scheduler (Coming Soon). 253 |
254 | 255 |
256 | 25-sse (Coming Soon) 257 | 258 | - **Description**: Boilerplate providing an example of using Server-Sent Events (SSE) in a Nest application (Coming Soon). 259 | - **Features**: SSE gateway implementation, sending real-time events to the client (Coming Soon). 260 |
261 | 262 |
263 | 26-file-upload (Coming Soon) 264 | 265 | - **Description**: A Nest boilerplate showcasing file upload implementation and management using Multer middleware (Coming Soon). 266 | - **Features**: File upload and management, example code (Coming Soon). 267 |
268 | 269 |
270 | 27-event-emitter (Coming Soon) 271 | 272 | - **Description**: Provides an event emitter API example within a Nest application (Coming Soon). 273 | - **Features**: Broadcasting events using EventGateway (RabbitMQ, Websockets), and relevant code (Coming Soon). 274 |
275 | 276 |
277 | 28-graphql-federation-code-first (Coming Soon) 278 | 279 | - **Description**: Boilerplate demonstrating how to implement a Code-First approach with cases using the GraphQL Federation protocol (Coming Soon). 280 | - **Features**: Federated schema composition, example queries and mutations (Coming Soon). 281 |
282 | 283 | 284 |
285 | 286 | ## Coming Soon: Nest Boilerplate CLI!! 287 | 288 | > [!NOTE] 289 | > Future Feature: CLI for creating NestJS projects based on boilerplates. 290 | 291 | We are excited to announce that in the near future, we will be introducing a command-line interface (CLI) for the Nest Boilerplates project. This CLI will make it even easier to create NestJS projects based on any of our available boilerplates. 292 | 293 | No more hassle or manual setup! With just a simple command, you will be able to create a fully functional NestJS project using your desired boilerplate. 294 | 295 | Here's a sneak peek of how it will work: 296 | 297 | 1. Install the Nest Boilerplate CLI globally on your machine. 298 | 299 | ``` 300 | npm install -g nest-boilerplate-cli 301 | ``` 302 | 303 | 2. Choose your desired boilerplate from our collection. For example, let's say you want to create a project using the "mongoose" boilerplate. 304 | 305 | 3. Run the CLI command, specifying the boilerplate you wish to use. 306 | 307 | ``` 308 | nest-boilerplate-cli create --boilerplate=mongoose 309 | ``` 310 | 311 | 4. Sit back and relax while the Nest Boilerplate CLI takes care of the heavy lifting, creating your project based on the selected boilerplate. 312 | 313 | Once the process is complete, you will have a fully configured NestJS project, ready to be customized 314 | 315 | ## License 316 | This project is licensed under the [MIT License](LICENSE). 317 | 318 | --- 319 | 320 | Thank you for your interest in the Nest Boilerplates project! We appreciate your contribution and hope that these boilerplates help you to quickly get started on your next Nest based project. Feel free to reach out with any suggestions, feedback, or more contributions to improve the project further. Let's make development a better experience together! 321 | --------------------------------------------------------------------------------