├── .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 |
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 | -
40 | About The Project
41 |
44 |
45 | -
46 | Getting Started
47 |
51 |
52 | - Usage
53 | - Roadmap
54 | - Contributing
55 | - License
56 | - Contact
57 | - Acknowledgments
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 |
--------------------------------------------------------------------------------