├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vs └── slnx.sqlite ├── README.md ├── apps ├── .gitkeep └── eshop │ ├── .eslintrc.json │ ├── jest.config.js │ ├── project.json │ ├── src │ ├── app │ │ ├── .gitkeep │ │ ├── app.module.ts │ │ ├── app.service.spec.ts │ │ ├── app.service.ts │ │ ├── basket │ │ │ └── basket.module.ts │ │ ├── catalog │ │ │ ├── catalog.health.ts │ │ │ ├── catalog.module.ts │ │ │ ├── commands │ │ │ │ └── create-catalog.command.ts │ │ │ ├── controllers │ │ │ │ └── catalog │ │ │ │ │ ├── catalog.controller.spec.ts │ │ │ │ │ └── catalog.controller.ts │ │ │ ├── handlers │ │ │ │ └── create-catalog.handler.ts │ │ │ └── models │ │ │ │ └── catalog-brand.model.ts │ │ ├── health │ │ │ ├── health.controller.spec.ts │ │ │ ├── health.controller.ts │ │ │ └── health.module.ts │ │ ├── ordering │ │ │ └── ordering.module.ts │ │ └── payment │ │ │ └── payment.module.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ └── main.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── jest.config.js ├── jest.preset.js ├── libs ├── .gitkeep └── common │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── project.json │ ├── src │ ├── index.ts │ └── lib │ │ ├── common.module.ts │ │ └── exceptions │ │ ├── custom-api-exception.spec.ts │ │ ├── custom-api-exception.ts │ │ ├── global-exception.filter.spec.ts │ │ ├── global-exception.filter.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── nx.json ├── package-lock.json ├── package.json ├── tools ├── generators │ └── .gitkeep └── tsconfig.tools.json ├── tsconfig.base.json ├── workspace.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nrwl/nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nrwl/nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nrwl/nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nrwl/nx/javascript"], 32 | "rules": {} 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imatiqul/nestjs-microservices/d14dbd2c8c0e346f6f1a8a78a442dc9caeb3839e/.vs/slnx.sqlite -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 | 7 | 10 | [![Issues][issues-shield]][issues-url] 11 | [![MIT License][license-shield]][license-url] 12 | [![LinkedIn][linkedin-shield]][linkedin-url] 13 | 14 | 15 |
16 |
17 | 18 | Logo 19 | 20 | 21 |

Nestjs Microservices

22 | 23 |

24 | Sample Nestjs Microservices application, based on a simplified Vertical Slice Architecture and best practices using Nestjs and Nodejs. 25 |
26 | Explore the docs » 27 |
28 |
29 | Report Bug 30 | · 31 | Request Feature 32 |

33 |
34 | 35 | 36 |
37 | Table of Contents 38 |
    39 |
  1. 40 | About The Project 41 | 44 |
  2. 45 |
  3. 46 | Getting Started 47 | 51 |
  4. 52 |
  5. Usage
  6. 53 |
  7. Roadmap
  8. 54 |
  9. Contributing
  10. 55 |
  11. License
  12. 56 |
  13. Contact
  14. 57 |
  15. Acknowledgments
  16. 58 |
59 |
60 | 61 | 62 | ## About The Project 63 | 64 | While designing architecture of REST API using Nestjs, we need to consider following areas 65 | | Areas | Node.js | 66 | | ----- | ----- | 67 | | API Security | Nestjs JwtModule | 68 | | API Versioning | Nestjs URL Versioning | 69 | | API Validation | Nestjs ValidationPipe | 70 | | API Documentation | Nestjs OpenApi | 71 | | Using DTOs (Object to Object Mapper) | TypeORM | 72 | | CORS Policy | Nestjs CORS | 73 | | Health Check | Healthchecks (Terminus) | 74 | | Dependency Injection | Built-in DI | 75 | | Logging | Nestjs-OpenTelemetry | 76 | | ORM | TypeORM | 77 | | CQRS pattern | Nestjs CQRS | 78 | | JSON Serialization | class-transformer | 79 | | Cross-cutting API calls | Nestjs ClientProxy | 80 | | Handle Errors Globally | useGlobalFilters | 81 | | Keep common code paths fast | Middleware | 82 | | Caching | Nestjs Caching | 83 | | Data Protection | Nestjs Security | 84 | | Avoid blocking calls | Asynchronous providers | 85 | | Complete long-running Tasks outside of HTTP requests | Nestjs Schedule | 86 | 87 | Here's why: 88 | * Vertical Slice Architecture 89 | * Best practices for Microservices 90 | * Low Code 91 | * 92 | 93 |

(back to top)

94 | 95 | ### Built With 96 | 97 | This section should list any major frameworks/libraries used to bootstrap the project. 98 | 99 | * [Node.js](https://nodejs.org/en/) 100 | * [Yarn](https://yarnpkg.com/) 101 | * [Nestjs](https://nestjs.com/) 102 | * [Nest Plugin](https://nx.dev/nest/overview) 103 | 104 |

(back to top)

105 | 106 | 107 | ## Getting Started 108 | 109 | Instructions on setting up the project locally. 110 | 111 | ### Prerequisites 112 | * Node.js 113 | * npm 114 | ```sh 115 | npm install npm@latest -g 116 | ``` 117 | * yarn 118 | ```sh 119 | npm install yarn -g 120 | ``` 121 | 122 | ### How to Run 123 | 124 | _Below is the instructions of how you can install and run the app._ 125 | 126 | 1. Clone the repo 127 | ```sh 128 | git clone https://github.com/imatiqul/nestjs-microservices.git 129 | ``` 130 | 2. Run project 131 | ```sh 132 | yarn 133 | yarn start 134 | ``` 135 |

(back to top)

136 | 137 | 138 | ## Usage 139 | 140 |

(back to top)

141 | 142 | 143 | ## Roadmap 144 | 145 |

(back to top)

146 | 147 | 148 | ## Contributing 149 | 150 | 1. Fork the Project 151 | 2. Create your Feature Branch (`git checkout -b feature/mf-feature`) 152 | 3. Commit your Changes (`git commit -m 'Add some mf-feature'`) 153 | 4. Push to the Branch (`git push origin feature/mf-feature`) 154 | 5. Open a Pull Request 155 | 156 |

(back to top)

157 | 158 | 159 | ## License 160 | 161 | Distributed under the MIT License. See `LICENSE.txt` for more information. 162 | 163 |

(back to top)

164 | 165 | 166 | ## Contact 167 | 168 | ATIQUL ISLAM - [@ATIQ](https://imatiqul.com/) - islam.md.atiqul@gmail.com 169 | 170 | Project Link: [https://github.com/imatiqul/nestjs-microservices](https://github.com/imatiqul/nestjs-microservices) 171 | 172 |

(back to top)

173 | 174 | 175 | ## Acknowledgments 176 | 177 |

(back to top)

178 | 179 | 180 | 181 | [issues-shield]: https://img.shields.io/github/issues/imatiqul/nestjs-microservices.svg?style=for-the-badge 182 | [issues-url]: https://github.com/imatiqul/nestjs-microservices/issues 183 | [license-shield]: https://img.shields.io/github/license/imatiqul/nestjs-microservices?style=for-the-badge 184 | [license-url]: https://github.com/imatiqul/nestjs-microservices/blob/master/LICENSE.txt 185 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 186 | [linkedin-url]: https://www.linkedin.com/in/mdatiqulislam/ 187 | -------------------------------------------------------------------------------- /apps/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imatiqul/nestjs-microservices/d14dbd2c8c0e346f6f1a8a78a442dc9caeb3839e/apps/.gitkeep -------------------------------------------------------------------------------- /apps/eshop/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/eshop/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'eshop', 3 | preset: '../../jest.preset.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsconfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]s$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'js', 'html'], 14 | coverageDirectory: '../../coverage/apps/eshop', 15 | }; 16 | -------------------------------------------------------------------------------- /apps/eshop/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "apps/eshop", 3 | "sourceRoot": "apps/eshop/src", 4 | "projectType": "application", 5 | "targets": { 6 | "build": { 7 | "executor": "@nrwl/node:build", 8 | "outputs": ["{options.outputPath}"], 9 | "options": { 10 | "outputPath": "dist/apps/eshop", 11 | "main": "apps/eshop/src/main.ts", 12 | "tsConfig": "apps/eshop/tsconfig.app.json", 13 | "assets": ["apps/eshop/src/assets"] 14 | }, 15 | "configurations": { 16 | "production": { 17 | "optimization": true, 18 | "extractLicenses": true, 19 | "inspect": false, 20 | "fileReplacements": [ 21 | { 22 | "replace": "apps/eshop/src/environments/environment.ts", 23 | "with": "apps/eshop/src/environments/environment.prod.ts" 24 | } 25 | ] 26 | } 27 | } 28 | }, 29 | "serve": { 30 | "executor": "@nrwl/node:execute", 31 | "options": { 32 | "buildTarget": "eshop:build" 33 | } 34 | }, 35 | "lint": { 36 | "executor": "@nrwl/linter:eslint", 37 | "outputs": ["{options.outputFile}"], 38 | "options": { 39 | "lintFilePatterns": ["apps/eshop/**/*.ts"] 40 | } 41 | }, 42 | "test": { 43 | "executor": "@nrwl/jest:jest", 44 | "outputs": ["coverage/apps/eshop"], 45 | "options": { 46 | "jestConfig": "apps/eshop/jest.config.js", 47 | "passWithNoTests": true 48 | } 49 | } 50 | }, 51 | "tags": [] 52 | } 53 | -------------------------------------------------------------------------------- /apps/eshop/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imatiqul/nestjs-microservices/d14dbd2c8c0e346f6f1a8a78a442dc9caeb3839e/apps/eshop/src/app/.gitkeep -------------------------------------------------------------------------------- /apps/eshop/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { AppService } from './app.service'; 4 | import { CatalogModule } from './catalog/catalog.module'; 5 | import { BasketModule } from './basket/basket.module'; 6 | import { OrderingModule } from './ordering/ordering.module'; 7 | import { PaymentModule } from './payment/payment.module'; 8 | import { RouterModule } from '@nestjs/core'; 9 | 10 | import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; 11 | import { ActiveHandlesMetric, ControllerInjector, EventEmitterInjector, GuardInjector, HttpRequestDurationSeconds, LoggerInjector, OpenTelemetryModule, PipeInjector, ScheduleInjector } from '@metinseylan/nestjs-opentelemetry'; 12 | import { ZipkinExporter } from '@opentelemetry/exporter-zipkin'; 13 | import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; 14 | import { HealthModule } from './health/health.module'; 15 | import { CommonModule } from '@demo/common'; 16 | 17 | @Module({ 18 | imports: [ 19 | CommonModule, 20 | HealthModule, 21 | CatalogModule, BasketModule, OrderingModule, PaymentModule, 22 | RouterModule.register([ 23 | { 24 | path: 'catalogs', 25 | module: CatalogModule, 26 | }, 27 | ]), 28 | OpenTelemetryModule.forRoot({ 29 | traceAutoInjectors: [ 30 | ControllerInjector, 31 | GuardInjector, 32 | EventEmitterInjector, 33 | ScheduleInjector, 34 | PipeInjector, 35 | LoggerInjector, 36 | ], 37 | metricAutoObservers: [ 38 | HttpRequestDurationSeconds.build({ 39 | boundaries: [20, 30, 100], 40 | }), 41 | ActiveHandlesMetric, 42 | ], 43 | metricExporter: new PrometheusExporter({ 44 | endpoint: 'metrics', 45 | port: 9464, 46 | }), 47 | metricInterval: 1000, 48 | spanProcessor: new SimpleSpanProcessor( 49 | new ZipkinExporter({ 50 | url: '//localhost:9411/zipkin/', 51 | }) 52 | ), 53 | }) 54 | ], 55 | providers: [AppService], 56 | }) 57 | export class AppModule {} 58 | -------------------------------------------------------------------------------- /apps/eshop/src/app/app.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppService', () => { 6 | let service: AppService; 7 | 8 | beforeAll(async () => { 9 | const app = await Test.createTestingModule({ 10 | providers: [AppService], 11 | }).compile(); 12 | 13 | service = app.get(AppService); 14 | }); 15 | 16 | describe('getData', () => { 17 | it('should return "Welcome to eshop!"', () => { 18 | expect(service.getData()).toEqual({ message: 'Welcome to eshop!' }); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/eshop/src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getData(): { message: string } { 6 | return { message: 'Welcome to eshop!' }; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/eshop/src/app/basket/basket.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class BasketModule {} 5 | -------------------------------------------------------------------------------- /apps/eshop/src/app/catalog/catalog.health.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | import { 4 | HealthIndicatorResult, 5 | HealthIndicator, 6 | HealthCheckError, 7 | } from '@nestjs/terminus'; 8 | 9 | @Injectable() 10 | export class CatalogHealthIndicator extends HealthIndicator { 11 | constructor() { 12 | super(); 13 | } 14 | 15 | async isHealthy(key: string): Promise { 16 | const badboys = []; 17 | const isHealthy = true; 18 | 19 | const result = this.getStatus(key, isHealthy, { badboys: badboys.length }); 20 | 21 | if (isHealthy) { 22 | return result; 23 | } 24 | 25 | throw new HealthCheckError('Catalogcheck failed', result); 26 | } 27 | } -------------------------------------------------------------------------------- /apps/eshop/src/app/catalog/catalog.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CqrsModule } from '@nestjs/cqrs'; 3 | import { CatalogHealthIndicator } from './catalog.health'; 4 | import { CatalogController } from './controllers/catalog/catalog.controller'; 5 | import { CreateCatalogHandler } from './handlers/create-catalog.handler'; 6 | 7 | export const CommandHandlers = [CreateCatalogHandler]; 8 | 9 | @Module({ 10 | imports: [CqrsModule], 11 | controllers: [CatalogController], 12 | providers: [CatalogHealthIndicator, 13 | ...CommandHandlers], 14 | exports: [CatalogHealthIndicator] 15 | }) 16 | export class CatalogModule { } 17 | -------------------------------------------------------------------------------- /apps/eshop/src/app/catalog/commands/create-catalog.command.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateCatalogCommand { 4 | @IsNotEmpty() 5 | public catalogName: string; 6 | 7 | @IsNotEmpty() 8 | public price: number; 9 | 10 | constructor(catalogName: string, 11 | price: number 12 | ) { 13 | this.catalogName = catalogName; 14 | this.price = price; 15 | } 16 | } -------------------------------------------------------------------------------- /apps/eshop/src/app/catalog/controllers/catalog/catalog.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CatalogController } from './catalog.controller'; 3 | 4 | describe('CatalogController', () => { 5 | let controller: CatalogController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [CatalogController], 10 | }).compile(); 11 | 12 | controller = module.get(CatalogController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /apps/eshop/src/app/catalog/controllers/catalog/catalog.controller.ts: -------------------------------------------------------------------------------- 1 | import { CustomApiException } from '@demo/common'; 2 | import { Body, Controller, Get, Logger, Post } from '@nestjs/common'; 3 | import { CommandBus } from '@nestjs/cqrs'; 4 | import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; 5 | import { CreateCatalogCommand } from '../../commands/create-catalog.command'; 6 | import { CatalogBrandModel } from '../../models/catalog-brand.model'; 7 | 8 | @ApiBearerAuth() 9 | @ApiTags('Catalogs') 10 | @Controller({ 11 | path: `/catalog`, 12 | version: '1' 13 | }) 14 | export class CatalogController { 15 | 16 | private readonly logger = new Logger(CatalogController.name); 17 | 18 | constructor(private commandBus: CommandBus) { 19 | 20 | } 21 | 22 | @Get('brands') 23 | @ApiOperation({ summary: 'Get Catalog Brands' }) 24 | @ApiResponse({ status: 403, description: 'Forbidden.' }) 25 | @ApiResponse({ 26 | status: 200, 27 | description: 'Catalog Brands', 28 | type: [CatalogBrandModel] 29 | }) 30 | async CatalogBrandsAsync(): Promise { 31 | this.logger.log('CatalogBrandsAsync'); 32 | const brands: CatalogBrandModel[] = []; 33 | 34 | brands.push({ id: 1, name: '1ABC' }); 35 | brands.push({ id: 2, name: '2ABC' }); 36 | brands.push({ id: 3, name: '3ABC' }); 37 | brands.push({ id: 4, name: '4ABC' }); 38 | 39 | return brands; 40 | } 41 | 42 | @Get('exception') 43 | @ApiOperation({ summary: 'Exception' }) 44 | @ApiResponse({ status: 500, description: 'CustomApiException' }) 45 | @ApiResponse({ 46 | status: 200, 47 | description: 'No Exception', 48 | type: Boolean 49 | }) 50 | async ExceptionAsync(): Promise { 51 | this.logger.log('ExceptionAsync'); 52 | throw new CustomApiException('CustomApiException from ExceptionAsync'); 53 | return true; 54 | } 55 | 56 | @Post() 57 | @ApiOperation({ summary: 'Create Catalog' }) 58 | @ApiResponse({ status: 403, description: 'Forbidden' }) 59 | @ApiResponse({ 60 | status: 201, 61 | description: 'Created Catalog Successfully', 62 | type: Boolean 63 | }) 64 | async CreateCatalogAsync(@Body() createCatalogCommand: CreateCatalogCommand): Promise { 65 | this.logger.log(`createCatalogCommand instanceof CreateCatalogCommand: ${createCatalogCommand instanceof CreateCatalogCommand}`) // true 66 | return this.commandBus.execute(createCatalogCommand); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /apps/eshop/src/app/catalog/handlers/create-catalog.handler.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@nestjs/common"; 2 | import { CommandHandler, ICommandHandler } from "@nestjs/cqrs"; 3 | import { CreateCatalogCommand } from "../commands/create-catalog.command"; 4 | 5 | @CommandHandler(CreateCatalogCommand) 6 | export class CreateCatalogHandler 7 | implements ICommandHandler { 8 | private readonly logger = new Logger(CreateCatalogHandler.name); 9 | 10 | async execute(command: CreateCatalogCommand) { 11 | this.logger.log(`CatalogName: ${command.catalogName} | Price: ${command.price}`); 12 | return true; 13 | } 14 | } -------------------------------------------------------------------------------- /apps/eshop/src/app/catalog/models/catalog-brand.model.ts: -------------------------------------------------------------------------------- 1 | export class CatalogBrandModel { 2 | id: number; 3 | name: string; 4 | 5 | } 6 | -------------------------------------------------------------------------------- /apps/eshop/src/app/health/health.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { HealthController } from './health.controller'; 3 | 4 | describe('HealthController', () => { 5 | let controller: HealthController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [HealthController], 10 | }).compile(); 11 | 12 | controller = module.get(HealthController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /apps/eshop/src/app/health/health.controller.ts: -------------------------------------------------------------------------------- 1 | import { HealthCheck, HealthCheckService } from '@nestjs/terminus'; 2 | import { Controller, Get } from '@nestjs/common'; 3 | import { CatalogHealthIndicator } from '../catalog/catalog.health'; 4 | 5 | @Controller('health') 6 | export class HealthController { 7 | constructor( 8 | private health: HealthCheckService, 9 | private catalogHealthIndicator: CatalogHealthIndicator 10 | ) {} 11 | 12 | @Get() 13 | @HealthCheck() 14 | healthCheck() { 15 | return this.health.check([ 16 | async () => this.catalogHealthIndicator.isHealthy('catalog'), 17 | ]) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/eshop/src/app/health/health.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TerminusModule } from '@nestjs/terminus'; 3 | import { CatalogModule } from '../catalog/catalog.module'; 4 | import { HealthController } from './health.controller'; 5 | 6 | @Module({ 7 | imports: [TerminusModule, CatalogModule], 8 | controllers: [HealthController], 9 | providers: [] 10 | }) 11 | export class HealthModule {} 12 | -------------------------------------------------------------------------------- /apps/eshop/src/app/ordering/ordering.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class OrderingModule {} 5 | -------------------------------------------------------------------------------- /apps/eshop/src/app/payment/payment.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class PaymentModule {} 5 | -------------------------------------------------------------------------------- /apps/eshop/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imatiqul/nestjs-microservices/d14dbd2c8c0e346f6f1a8a78a442dc9caeb3839e/apps/eshop/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/eshop/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/eshop/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/eshop/src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is not a production server yet! 3 | * This is only a minimal backend to get started. 4 | */ 5 | 6 | import { GlobalExceptionFilter } from '@demo/common'; 7 | import { Logger, ValidationPipe, VersioningType } from '@nestjs/common'; 8 | import { NestFactory } from '@nestjs/core'; 9 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; 10 | import { JsonLoggerService, RequestLogger, RequestLoggerOptions } from 'json-logger-service'; 11 | 12 | import { AppModule } from './app/app.module'; 13 | 14 | async function bootstrap() { 15 | const app = await NestFactory.create(AppModule); 16 | const globalPrefix = 'api'; 17 | app.setGlobalPrefix(globalPrefix); 18 | const port = process.env.PORT || 3333; 19 | 20 | // or "app.enableVersioning()" 21 | app.enableVersioning({ 22 | type: VersioningType.URI, 23 | }); 24 | 25 | const config = new DocumentBuilder() 26 | .setTitle('Nestjs Microservices') 27 | .setDescription('The Nestjs Microservices API description') 28 | .setVersion('1.0') 29 | .addBearerAuth() 30 | .build(); 31 | const document = SwaggerModule.createDocument(app, config); 32 | SwaggerModule.setup('swagger', app, document); 33 | 34 | app.useGlobalPipes(new ValidationPipe({ transform: true })); 35 | 36 | const sendInternalServerErrorCause = false; 37 | const logAllErrors = true; 38 | const globalExceptionFilter = new GlobalExceptionFilter(sendInternalServerErrorCause, logAllErrors); 39 | app.useGlobalFilters(globalExceptionFilter); 40 | 41 | await app.listen(port); 42 | Logger.log( 43 | `🚀 Application is running on: http://localhost:${port}/${globalPrefix}` 44 | ); 45 | } 46 | 47 | bootstrap(); 48 | -------------------------------------------------------------------------------- /apps/eshop/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["node"], 7 | "emitDecoratorMetadata": true, 8 | "target": "es2015" 9 | }, 10 | "exclude": ["**/*.spec.ts", "**/*.test.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /apps/eshop/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/eshop/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { getJestProjects } = require('@nrwl/jest'); 2 | 3 | module.exports = { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset'); 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imatiqul/nestjs-microservices/d14dbd2c8c0e346f6f1a8a78a442dc9caeb3839e/libs/.gitkeep -------------------------------------------------------------------------------- /libs/common/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /libs/common/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /libs/common/README.md: -------------------------------------------------------------------------------- 1 | # common 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test common` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/common/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'common', 3 | preset: '../../jest.preset.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsconfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../coverage/libs/common', 15 | }; 16 | -------------------------------------------------------------------------------- /libs/common/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "libs/common", 3 | "sourceRoot": "libs/common/src", 4 | "projectType": "library", 5 | "targets": { 6 | "lint": { 7 | "executor": "@nrwl/linter:eslint", 8 | "outputs": ["{options.outputFile}"], 9 | "options": { 10 | "lintFilePatterns": ["libs/common/**/*.ts"] 11 | } 12 | }, 13 | "test": { 14 | "executor": "@nrwl/jest:jest", 15 | "outputs": ["coverage/libs/common"], 16 | "options": { 17 | "jestConfig": "libs/common/jest.config.js", 18 | "passWithNoTests": true 19 | } 20 | } 21 | }, 22 | "tags": [] 23 | } 24 | -------------------------------------------------------------------------------- /libs/common/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/common.module'; 2 | 3 | export * from './lib/exceptions/index'; 4 | -------------------------------------------------------------------------------- /libs/common/src/lib/common.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({ 4 | controllers: [], 5 | providers: [] 6 | }) 7 | export class CommonModule {} 8 | -------------------------------------------------------------------------------- /libs/common/src/lib/exceptions/custom-api-exception.spec.ts: -------------------------------------------------------------------------------- 1 | import { CustomApiException } from './custom-api-exception'; 2 | 3 | describe('CustomApiException', () => { 4 | it('should be defined', () => { 5 | expect(new CustomApiException()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /libs/common/src/lib/exceptions/custom-api-exception.ts: -------------------------------------------------------------------------------- 1 | import {HttpStatus, InternalServerErrorException} from '@nestjs/common'; 2 | 3 | export class CustomApiException extends InternalServerErrorException { 4 | public constructor(message: string, public readonly causeError?: any) { 5 | super({message: message, statusCode: HttpStatus.INTERNAL_SERVER_ERROR}); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/common/src/lib/exceptions/global-exception.filter.spec.ts: -------------------------------------------------------------------------------- 1 | import { GlobalExceptionFilter } from './global-exception.filter'; 2 | 3 | describe('GlobalExceptionFilter', () => { 4 | it('should be defined', () => { 5 | expect(new GlobalExceptionFilter()).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /libs/common/src/lib/exceptions/global-exception.filter.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ArgumentsHost, Catch, ExceptionFilter, HttpStatus, Logger } from '@nestjs/common'; 3 | import { Request, Response } from 'express'; 4 | import { v1 as uuidv1 } from 'uuid'; 5 | import { CustomApiException } from './custom-api-exception'; 6 | 7 | @Catch() 8 | export class GlobalExceptionFilter implements ExceptionFilter { 9 | private static extractCustomApiException(error: any): string { 10 | if (!(error instanceof CustomApiException)) { 11 | return undefined; 12 | } 13 | 14 | if (!error.causeError) { 15 | return undefined; 16 | } 17 | 18 | if (error.causeError instanceof String) { 19 | return error.causeError as string; 20 | } 21 | 22 | if (!error.causeError.message && !error.causeError.response) { 23 | return undefined; 24 | } 25 | 26 | const integrationErrorDetails = { 27 | message: error.causeError.message, 28 | details: error.causeError.response && error.causeError.response.data, 29 | }; 30 | return JSON.stringify({ causeError: integrationErrorDetails }); 31 | } 32 | 33 | private logger = new Logger(GlobalExceptionFilter.name); 34 | 35 | public constructor(private readonly sendClientInternalServerErrorCause: boolean = false, 36 | private readonly logAllErrors: boolean = false, 37 | private readonly logErrorsWithStatusCode: number[] = []) { 38 | } 39 | 40 | public catch(exception: any, host: ArgumentsHost): any { 41 | const ctx = host.switchToHttp(); 42 | const response = ctx.getResponse(); 43 | const request = ctx.getRequest(); 44 | 45 | const responseStatus = exception.status ? exception.status : HttpStatus.INTERNAL_SERVER_ERROR; 46 | const messageObject = this.getBackwardsCompatibleMessageObject(exception, responseStatus); 47 | let errorId = undefined; 48 | let customApiException = undefined; 49 | 50 | if (responseStatus === HttpStatus.INTERNAL_SERVER_ERROR) { 51 | errorId = uuidv1(); 52 | customApiException = GlobalExceptionFilter.extractCustomApiException(exception); 53 | 54 | this.logger.error({ 55 | errorId: errorId, 56 | route: request.url, 57 | customApiException: customApiException, 58 | stack: exception.stack && JSON.stringify(exception.stack, ['stack'], 4), 59 | }, messageObject); 60 | } else if (this.logAllErrors || this.logErrorsWithStatusCode.indexOf(responseStatus) !== -1) { 61 | this.logger.error({ 62 | route: request.url, 63 | stack: exception.stack && JSON.stringify(exception.stack), 64 | }, messageObject); 65 | } 66 | 67 | response 68 | .status(responseStatus) 69 | .json({ 70 | errorId: errorId, 71 | message: this.getClientResponseMessage(responseStatus, exception), 72 | integrationErrorDetails: responseStatus === HttpStatus.INTERNAL_SERVER_ERROR && this.sendClientInternalServerErrorCause ? customApiException : undefined, 73 | }); 74 | } 75 | 76 | private getClientResponseMessage(responseStatus: number, exception: any): any { 77 | if (responseStatus !== HttpStatus.INTERNAL_SERVER_ERROR 78 | || (responseStatus === HttpStatus.INTERNAL_SERVER_ERROR && this.sendClientInternalServerErrorCause)) { 79 | return this.getBackwardsCompatibleMessageObject(exception, responseStatus); 80 | } 81 | 82 | return 'Internal server error.'; 83 | } 84 | 85 | private getBackwardsCompatibleMessageObject(exception: any, responseStatus: number): any { 86 | const errorResponse = exception.response; 87 | if (errorResponse && errorResponse.error) { 88 | return { error: errorResponse.error, message: errorResponse.message, statusCode: responseStatus }; 89 | } 90 | return { error: exception.message, statusCode: responseStatus }; 91 | } 92 | } -------------------------------------------------------------------------------- /libs/common/src/lib/exceptions/index.ts: -------------------------------------------------------------------------------- 1 | // exceptions 2 | export * from './custom-api-exception'; 3 | export * from './global-exception.filter'; 4 | -------------------------------------------------------------------------------- /libs/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/common/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"], 8 | "target": "es6" 9 | }, 10 | "exclude": ["**/*.spec.ts", "**/*.test.ts"], 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /libs/common/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.test.ts", 10 | "**/*.spec.ts", 11 | "**/*.test.tsx", 12 | "**/*.spec.tsx", 13 | "**/*.test.js", 14 | "**/*.spec.js", 15 | "**/*.test.jsx", 16 | "**/*.spec.jsx", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmScope": "demo", 3 | "affected": { 4 | "defaultBase": "master" 5 | }, 6 | "cli": { 7 | "defaultCollection": "@nrwl/nest" 8 | }, 9 | "implicitDependencies": { 10 | "package.json": { 11 | "dependencies": "*", 12 | "devDependencies": "*" 13 | }, 14 | ".eslintrc.json": "*" 15 | }, 16 | "tasksRunnerOptions": { 17 | "default": { 18 | "runner": "@nrwl/nx-cloud", 19 | "options": { 20 | "cacheableOperations": [ 21 | "build", 22 | "lint", 23 | "test", 24 | "e2e" 25 | ], 26 | "accessToken": "NDdiMWQyMzAtYjhhOC00N2U1LTk2MDUtMDlhNTUwODJjNGQwfHJlYWQtd3JpdGU=" 27 | } 28 | } 29 | }, 30 | "targetDependencies": { 31 | "build": [ 32 | { 33 | "target": "build", 34 | "projects": "dependencies" 35 | } 36 | ] 37 | }, 38 | "defaultProject": "eshop" 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "nx serve", 7 | "build": "nx build", 8 | "test": "nx test" 9 | }, 10 | "private": true, 11 | "dependencies": { 12 | "@metinseylan/nestjs-opentelemetry": "^2.0.3", 13 | "@nestjs/common": "^8.0.0", 14 | "@nestjs/core": "^8.0.0", 15 | "@nestjs/cqrs": "^8.0.2", 16 | "@nestjs/platform-express": "^8.0.0", 17 | "@nestjs/swagger": "^5.2.0", 18 | "@nestjs/terminus": "^8.0.4", 19 | "@opentelemetry/exporter-prometheus": "^0.27.0", 20 | "@opentelemetry/exporter-zipkin": "^1.0.1", 21 | "class-transformer": "^0.5.1", 22 | "class-validator": "^0.13.2", 23 | "json-logger-service": "^8.0.2", 24 | "reflect-metadata": "^0.1.13", 25 | "rxjs": "^7.0.0", 26 | "swagger-ui-express": "^4.3.0", 27 | "tslib": "^2.0.0", 28 | "uuid": "^8.3.2" 29 | }, 30 | "devDependencies": { 31 | "@nestjs/schematics": "^8.0.0", 32 | "@nestjs/testing": "^8.0.0", 33 | "@nrwl/cli": "13.8.3", 34 | "@nrwl/eslint-plugin-nx": "13.8.3", 35 | "@nrwl/jest": "13.8.3", 36 | "@nrwl/linter": "13.8.3", 37 | "@nrwl/nest": "13.8.3", 38 | "@nrwl/node": "13.8.3", 39 | "@nrwl/nx-cloud": "latest", 40 | "@nrwl/tao": "13.8.3", 41 | "@nrwl/workspace": "13.8.3", 42 | "@types/jest": "27.0.2", 43 | "@types/node": "16.11.7", 44 | "@typescript-eslint/eslint-plugin": "~5.10.0", 45 | "@typescript-eslint/parser": "~5.10.0", 46 | "eslint": "~8.7.0", 47 | "eslint-config-prettier": "8.1.0", 48 | "jest": "27.2.3", 49 | "prettier": "^2.5.1", 50 | "ts-jest": "27.0.5", 51 | "typescript": "~4.5.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imatiqul/nestjs-microservices/d14dbd2c8c0e346f6f1a8a78a442dc9caeb3839e/tools/generators/.gitkeep -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@demo/common": ["libs/common/src/index.ts"] 19 | } 20 | }, 21 | "exclude": ["node_modules", "tmp"] 22 | } 23 | -------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "projects": { 4 | "common": "libs/common", 5 | "eshop": "apps/eshop" 6 | } 7 | } 8 | --------------------------------------------------------------------------------