├── libs ├── .gitkeep ├── heart-beat │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ └── heart-beat.ts │ ├── tslint.json │ ├── tsconfig.json │ ├── README.md │ ├── tsconfig.lib.json │ ├── jest.config.js │ └── tsconfig.spec.json ├── api-interfaces │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ └── api-interfaces.ts │ ├── tslint.json │ ├── tsconfig.json │ ├── README.md │ ├── tsconfig.lib.json │ ├── jest.config.js │ └── tsconfig.spec.json └── service-manager │ ├── src │ ├── index.ts │ └── lib │ │ └── service-manager.ts │ ├── tslint.json │ ├── tsconfig.json │ ├── README.md │ ├── tsconfig.lib.json │ ├── jest.config.js │ └── tsconfig.spec.json ├── apps ├── .gitkeep ├── api │ ├── src │ │ ├── app │ │ │ ├── .gitkeep │ │ │ ├── app.service.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.spec.ts │ │ │ └── app.controller.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ └── main.ts │ ├── tslint.json │ ├── jest.config.js │ ├── tsconfig.json │ ├── tsconfig.app.json │ └── tsconfig.spec.json ├── my-app │ ├── src │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── test-setup.ts │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── styles.scss │ │ ├── app │ │ │ ├── app.component.html │ │ │ ├── app.module.ts │ │ │ ├── app.component.ts │ │ │ ├── app.component.spec.ts │ │ │ └── app.component.scss │ │ ├── index.html │ │ ├── main.ts │ │ └── polyfills.ts │ ├── proxy.conf.json │ ├── tsconfig.json │ ├── tslint.json │ ├── tsconfig.spec.json │ ├── tsconfig.app.json │ ├── jest.config.js │ └── browserslist ├── service-a │ ├── src │ │ ├── app │ │ │ ├── .gitkeep │ │ │ ├── app.service.ts │ │ │ ├── app.module.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.service.spec.ts │ │ │ └── app.controller.spec.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.ts │ │ │ └── environment.prod.ts │ │ └── main.ts │ ├── tslint.json │ ├── jest.config.js │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── service-b │ ├── src │ │ ├── app │ │ │ ├── .gitkeep │ │ │ ├── app.service.ts │ │ │ ├── app.module.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.service.spec.ts │ │ │ └── app.controller.spec.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.ts │ │ │ └── environment.prod.ts │ │ └── main.ts │ ├── tslint.json │ ├── jest.config.js │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json └── my-app-e2e │ ├── tslint.json │ ├── src │ ├── support │ │ ├── app.po.ts │ │ ├── index.ts │ │ └── commands.ts │ ├── fixtures │ │ └── example.json │ ├── integration │ │ └── app.spec.ts │ └── plugins │ │ └── index.js │ ├── tsconfig.json │ ├── tsconfig.e2e.json │ └── cypress.json ├── tools ├── schematics │ └── .gitkeep └── tsconfig.tools.json ├── .prettierrc ├── .prettierignore ├── .vscode └── extensions.json ├── .editorconfig ├── jest.config.js ├── .gitignore ├── nx.json ├── tsconfig.json ├── package.json ├── tslint.json ├── README.md └── angular.json /libs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /apps/api/src/app/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/api/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/schematics/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/my-app/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/service-a/src/app/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/service-b/src/app/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/service-a/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/service-b/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /apps/my-app/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /apps/service-a/tslint.json: -------------------------------------------------------------------------------- 1 | {"extends":"../../tslint.json","rules":[]} -------------------------------------------------------------------------------- /apps/service-b/tslint.json: -------------------------------------------------------------------------------- 1 | {"extends":"../../tslint.json","rules":[]} -------------------------------------------------------------------------------- /libs/heart-beat/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/heart-beat'; 2 | -------------------------------------------------------------------------------- /apps/api/tslint.json: -------------------------------------------------------------------------------- 1 | { "extends": "../../tslint.json", "rules": [] } 2 | -------------------------------------------------------------------------------- /libs/api-interfaces/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/api-interfaces'; 2 | -------------------------------------------------------------------------------- /libs/service-manager/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/service-manager'; 2 | -------------------------------------------------------------------------------- /apps/my-app-e2e/tslint.json: -------------------------------------------------------------------------------- 1 | { "extends": "../../tslint.json", "rules": [] } 2 | -------------------------------------------------------------------------------- /libs/api-interfaces/tslint.json: -------------------------------------------------------------------------------- 1 | { "extends": "../../tslint.json", "rules": [] } 2 | -------------------------------------------------------------------------------- /libs/heart-beat/tslint.json: -------------------------------------------------------------------------------- 1 | { "extends": "../../tslint.json", "rules": [] } 2 | -------------------------------------------------------------------------------- /apps/my-app-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /libs/service-manager/tslint.json: -------------------------------------------------------------------------------- 1 | { "extends": "../../tslint.json", "rules": [] } 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /apps/api/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /apps/api/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false 3 | }; 4 | -------------------------------------------------------------------------------- /apps/my-app/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /apps/my-app/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danmt/service-discovery/HEAD/apps/my-app/src/favicon.ico -------------------------------------------------------------------------------- /apps/my-app/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /apps/service-a/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false 3 | }; 4 | -------------------------------------------------------------------------------- /apps/service-b/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false 3 | }; 4 | -------------------------------------------------------------------------------- /apps/service-a/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /apps/service-b/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /apps/my-app/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:3333", 4 | "secure": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/my-app-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/api/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'api', 3 | preset: '../../jest.config.js', 4 | coverageDirectory: '../../coverage/apps/api' 5 | }; 6 | -------------------------------------------------------------------------------- /apps/my-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /apps/my-app-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["cypress", "node"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /apps/service-a/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'service-a', 3 | preset: '../../jest.config.js', 4 | coverageDirectory: '../../coverage/apps/service-a' 5 | }; 6 | -------------------------------------------------------------------------------- /apps/service-b/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'service-b', 3 | preset: '../../jest.config.js', 4 | coverageDirectory: '../../coverage/apps/service-b' 5 | }; 6 | -------------------------------------------------------------------------------- /libs/heart-beat/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /libs/api-interfaces/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /libs/service-manager/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /libs/api-interfaces/src/lib/api-interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Message { 2 | message: string; 3 | } 4 | 5 | export interface ComposedMessage { 6 | serviceA: string; 7 | serviceB: string; 8 | } 9 | -------------------------------------------------------------------------------- /apps/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "jest"], 5 | "emitDecoratorMetadata": true 6 | }, 7 | "include": ["**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/my-app-e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc" 6 | }, 7 | "include": ["src/**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "angular.ng-template", 5 | "ms-vscode.vscode-typescript-tslint-plugin", 6 | "esbenp.prettier-vscode" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /libs/heart-beat/README.md: -------------------------------------------------------------------------------- 1 | # heart-beat 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test heart-beat` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /apps/my-app/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "myOrg", "camelCase"], 5 | "component-selector": [true, "element", "my-org", "kebab-case"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/api-interfaces/README.md: -------------------------------------------------------------------------------- 1 | # api-interfaces 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test api-interfaces` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/service-manager/README.md: -------------------------------------------------------------------------------- 1 | # service-manager 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test service-manager` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /apps/service-a/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 service-a!' }); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/service-b/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 service-b!' }); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/api/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "exclude": ["**/*.spec.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/heart-beat/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "exclude": ["**/*.spec.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/service-a/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "exclude": ["**/*.spec.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/service-b/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "exclude": ["**/*.spec.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/api-interfaces/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "exclude": ["**/*.spec.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/service-manager/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "exclude": ["**/*.spec.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/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": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/service-a/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": [ 5 | "node", 6 | "jest" 7 | ], 8 | "emitDecoratorMetadata": true 9 | }, 10 | "include": [ 11 | "**/*.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/service-b/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": [ 5 | "node", 6 | "jest" 7 | ], 8 | "emitDecoratorMetadata": true 9 | }, 10 | "include": [ 11 | "**/*.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/api/src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Message } from '@my-org/api-interfaces'; 3 | 4 | @Injectable() 5 | export class AppService { 6 | getData(): Message { 7 | return { message: 'Welcome to api!' }; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/my-app/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Welcome to my-app!

3 | 7 |
8 |
Message: {{ hello$ | async | json }}
9 | -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"] 9 | }, 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /apps/service-a/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 | "**/*.spec.ts", 10 | "**/*.d.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /apps/service-b/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 | "**/*.spec.ts", 10 | "**/*.d.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /apps/my-app/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/my-app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts", "src/polyfills.ts"], 8 | "include": ["**/*.ts"], 9 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/heart-beat/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'heart-beat', 3 | preset: '../../jest.config.js', 4 | transform: { 5 | '^.+\\.[tj]sx?$': 'ts-jest' 6 | }, 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], 8 | coverageDirectory: '../../coverage/libs/heart-beat' 9 | }; 10 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /apps/my-app/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'my-app', 3 | preset: '../../jest.config.js', 4 | coverageDirectory: '../../coverage/apps/my-app', 5 | snapshotSerializers: [ 6 | 'jest-preset-angular/AngularSnapshotSerializer.js', 7 | 'jest-preset-angular/HTMLCommentSerializer.js' 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /libs/api-interfaces/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'api-interfaces', 3 | preset: '../../jest.config.js', 4 | transform: { 5 | '^.+\\.[tj]sx?$': 'ts-jest' 6 | }, 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], 8 | coverageDirectory: '../../coverage/libs/api-interfaces' 9 | }; 10 | -------------------------------------------------------------------------------- /libs/service-manager/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'service-manager', 3 | preset: '../../jest.config.js', 4 | transform: { 5 | '^.+\\.[tj]sx?$': 'ts-jest' 6 | }, 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], 8 | coverageDirectory: '../../coverage/libs/service-manager' 9 | }; 10 | -------------------------------------------------------------------------------- /apps/service-a/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | 6 | @Module({ 7 | imports: [], 8 | controllers: [AppController], 9 | providers: [AppService], 10 | }) 11 | export class AppModule {} 12 | -------------------------------------------------------------------------------- /apps/service-b/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | 6 | @Module({ 7 | imports: [], 8 | controllers: [AppController], 9 | providers: [AppService], 10 | }) 11 | export class AppModule {} 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'], 3 | transform: { 4 | '^.+\\.(ts|js|html)$': 'ts-jest' 5 | }, 6 | resolver: '@nrwl/jest/plugins/resolver', 7 | moduleFileExtensions: ['ts', 'js', 'html'], 8 | coverageReporters: ['html'], 9 | passWithNoTests: true 10 | }; 11 | -------------------------------------------------------------------------------- /apps/api/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, HttpModule } from '@nestjs/common'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | 6 | @Module({ 7 | imports: [HttpModule], 8 | controllers: [AppController], 9 | providers: [AppService] 10 | }) 11 | export class AppModule {} 12 | -------------------------------------------------------------------------------- /apps/service-a/src/app/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | import { AppService } from './app.service'; 4 | 5 | @Controller() 6 | export class AppController { 7 | constructor(private readonly appService: AppService) {} 8 | 9 | @Get('hello') 10 | getData() { 11 | return this.appService.getData(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/service-b/src/app/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | import { AppService } from './app.service'; 4 | 5 | @Controller() 6 | export class AppController { 7 | constructor(private readonly appService: AppService) {} 8 | 9 | @Get('hello') 10 | getData() { 11 | return this.appService.getData(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /libs/heart-beat/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 | "**/*.spec.ts", 10 | "**/*.spec.tsx", 11 | "**/*.spec.js", 12 | "**/*.spec.jsx", 13 | "**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /libs/api-interfaces/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 | "**/*.spec.ts", 10 | "**/*.spec.tsx", 11 | "**/*.spec.js", 12 | "**/*.spec.jsx", 13 | "**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /libs/service-manager/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 | "**/*.spec.ts", 10 | "**/*.spec.tsx", 11 | "**/*.spec.js", 12 | "**/*.spec.jsx", 13 | "**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /apps/api/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | const port = process.env.port || 3333; 8 | await app.listen(port, () => { 9 | console.log(`Listening at http://localhost:${port}`); 10 | }); 11 | } 12 | 13 | bootstrap(); 14 | -------------------------------------------------------------------------------- /apps/my-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MyApp 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/my-app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /apps/my-app/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppComponent } from './app.component'; 5 | import { HttpClientModule } from '@angular/common/http'; 6 | 7 | @NgModule({ 8 | declarations: [AppComponent], 9 | imports: [BrowserModule, HttpClientModule], 10 | providers: [], 11 | bootstrap: [AppComponent] 12 | }) 13 | export class AppModule {} 14 | -------------------------------------------------------------------------------- /apps/my-app/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Message } from '@my-org/api-interfaces'; 4 | 5 | @Component({ 6 | selector: 'my-org-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.scss'] 9 | }) 10 | export class AppComponent { 11 | hello$ = this.http.get('/api/hello'); 12 | constructor(private http: HttpClient) {} 13 | } 14 | -------------------------------------------------------------------------------- /apps/my-app-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "pluginsFile": "./src/plugins/index", 7 | "supportFile": "./src/support/index.ts", 8 | "video": true, 9 | "videosFolder": "../../dist/cypress/apps/my-app-e2e/videos", 10 | "screenshotsFolder": "../../dist/cypress/apps/my-app-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/my-app-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('my-app', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to my-app!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/my-app/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /apps/api/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 api!"', () => { 18 | expect(service.getData()).toEqual({ message: 'Welcome to api!' }); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/service-a/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 service-a!"', () => { 18 | expect(service.getData()).toEqual({message: 'Welcome to service-a!'}); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/service-b/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 service-b!"', () => { 18 | expect(service.getData()).toEqual({message: 'Welcome to service-b!'}); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /apps/my-app-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/my-app/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TestBed, async } from '@angular/core/testing'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(async(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [AppComponent], 10 | imports: [HttpClientModule] 11 | }).compileComponents(); 12 | })); 13 | 14 | it('should create the app', () => { 15 | const fixture = TestBed.createComponent(AppComponent); 16 | const app = fixture.debugElement.componentInstance; 17 | expect(app).toBeTruthy(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmScope": "my-org", 3 | "implicitDependencies": { 4 | "angular.json": "*", 5 | "package.json": "*", 6 | "tsconfig.json": "*", 7 | "tslint.json": "*", 8 | "nx.json": "*" 9 | }, 10 | "projects": { 11 | "my-app-e2e": { 12 | "tags": [] 13 | }, 14 | "my-app": { 15 | "tags": [] 16 | }, 17 | "api": { 18 | "tags": [] 19 | }, 20 | "api-interfaces": { 21 | "tags": [] 22 | }, 23 | "service-a": { 24 | "tags": [] 25 | }, 26 | "service-b": { 27 | "tags": [] 28 | }, 29 | "heart-beat": { 30 | "tags": [] 31 | }, 32 | "service-manager": { 33 | "tags": [] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apps/service-a/src/main.ts: -------------------------------------------------------------------------------- 1 | import { HeartBeat } from '@my-org/heart-beat'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { AppModule } from './app/app.module'; 4 | 5 | const SERVICE_NAME = 'Service A'; 6 | 7 | async function bootstrap() { 8 | const app = await NestFactory.create(AppModule); 9 | 10 | app.use((req, res, next) => { 11 | heartBeat.requestArrived(); 12 | next(); 13 | }); 14 | 15 | const server = await app.listen(0); 16 | process.send({ port: server.address().port }); 17 | console.log( 18 | `${SERVICE_NAME} running on http://localhost:${server.address().port}` 19 | ); 20 | 21 | const heartBeat = new HeartBeat(SERVICE_NAME, { explain: true }); 22 | heartBeat.init(); 23 | } 24 | 25 | bootstrap(); 26 | -------------------------------------------------------------------------------- /apps/service-b/src/main.ts: -------------------------------------------------------------------------------- 1 | import { HeartBeat } from '@my-org/heart-beat'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { AppModule } from './app/app.module'; 4 | 5 | const SERVICE_NAME = 'Service B'; 6 | 7 | async function bootstrap() { 8 | const app = await NestFactory.create(AppModule); 9 | 10 | app.use((req, res, next) => { 11 | heartBeat.requestArrived(); 12 | next(); 13 | }); 14 | 15 | const server = await app.listen(0); 16 | process.send({ port: server.address().port }); 17 | console.log( 18 | `${SERVICE_NAME} running on http://localhost:${server.address().port}` 19 | ); 20 | 21 | const heartBeat = new HeartBeat(SERVICE_NAME, { explain: true }); 22 | heartBeat.init(); 23 | } 24 | 25 | bootstrap(); 26 | -------------------------------------------------------------------------------- /apps/service-a/src/app/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | 6 | describe('AppController', () => { 7 | let app: TestingModule; 8 | 9 | beforeAll(async () => { 10 | app = await Test.createTestingModule({ 11 | controllers: [AppController], 12 | providers: [AppService], 13 | }).compile(); 14 | }); 15 | 16 | describe('getData', () => { 17 | it('should return "Welcome to service-a!"', () => { 18 | const appController = app.get(AppController); 19 | expect(appController.getData()).toEqual({message: 'Welcome to service-a!'}); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /apps/service-b/src/app/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | 6 | describe('AppController', () => { 7 | let app: TestingModule; 8 | 9 | beforeAll(async () => { 10 | app = await Test.createTestingModule({ 11 | controllers: [AppController], 12 | providers: [AppService], 13 | }).compile(); 14 | }); 15 | 16 | describe('getData', () => { 17 | it('should return "Welcome to service-b!"', () => { 18 | const appController = app.get(AppController); 19 | expect(appController.getData()).toEqual({message: 'Welcome to service-b!'}); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /apps/my-app/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /tsconfig.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 | "typeRoots": ["node_modules/@types"], 14 | "lib": ["es2017", "dom"], 15 | "skipLibCheck": true, 16 | "skipDefaultLibCheck": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@my-org/api-interfaces": ["libs/api-interfaces/src/index.ts"], 20 | "@my-org/heart-beat": ["libs/heart-beat/src/index.ts"], 21 | "@my-org/service-manager": ["libs/service-manager/src/index.ts"] 22 | } 23 | }, 24 | "exclude": ["node_modules", "tmp"] 25 | } 26 | -------------------------------------------------------------------------------- /apps/my-app-e2e/src/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor'); 15 | 16 | module.exports = (on, config) => { 17 | // `on` is used to hook into various events Cypress emits 18 | // `config` is the resolved Cypress config 19 | 20 | // Preprocess Typescript 21 | on('file:preprocessor', preprocessTypescript(config)); 22 | }; 23 | -------------------------------------------------------------------------------- /apps/my-app-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // eslint-disable-next-line @typescript-eslint/no-namespace 11 | declare namespace Cypress { 12 | interface Chainable { 13 | login(email: string, password: string): void; 14 | } 15 | } 16 | // 17 | // -- This is a parent command -- 18 | Cypress.Commands.add('login', (email, password) => { 19 | console.log('Custom command example: Login', email, password); 20 | }); 21 | // 22 | // -- This is a child command -- 23 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 24 | // 25 | // 26 | // -- This is a dual command -- 27 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 28 | // 29 | // 30 | // -- This will overwrite an existing command -- 31 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 32 | -------------------------------------------------------------------------------- /libs/heart-beat/src/lib/heart-beat.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | export interface HeartBeatConfig { 4 | maxIdleTimeInSeconds: number; 5 | beatStepInSeconds: number; 6 | explain: boolean; 7 | } 8 | 9 | export class HeartBeat { 10 | private readonly channel = new EventEmitter(); 11 | private readonly explain: boolean; 12 | private readonly maxIdleTimeInSeconds: number; 13 | private readonly beatStepInSeconds: number; 14 | private lastCallTimestamp = Date.now(); 15 | 16 | constructor( 17 | private readonly serviceName: string, 18 | config: Partial = {} 19 | ) { 20 | this.explain = config.explain || false; 21 | this.maxIdleTimeInSeconds = config.maxIdleTimeInSeconds || 10; 22 | this.beatStepInSeconds = config.beatStepInSeconds || 1; 23 | } 24 | 25 | init() { 26 | setInterval(() => { 27 | if (this.explain) { 28 | console.log(`${this.serviceName} Beating...`); 29 | console.log( 30 | 'Time passed since last request: ', 31 | (Date.now() - this.lastCallTimestamp) / 1000, 32 | 'seconds' 33 | ); 34 | } 35 | const idleSeconds = (Date.now() - this.lastCallTimestamp) / 1000; 36 | if (idleSeconds > this.maxIdleTimeInSeconds) { 37 | if (this.explain) { 38 | console.log(`${this.serviceName} has exitted`); 39 | } 40 | process.exit(); 41 | } 42 | }, this.beatStepInSeconds * 1000); 43 | 44 | this.channel.on('request-arrived', () => { 45 | this.lastCallTimestamp = Date.now(); 46 | }); 47 | } 48 | 49 | requestArrived() { 50 | if (this.explain) { 51 | console.log(`New request arrived...`); 52 | } 53 | this.channel.emit('request-arrived'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /libs/service-manager/src/lib/service-manager.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import { ChildProcess, fork } from 'child_process'; 3 | import { Observable, Observer } from 'rxjs'; 4 | import { mergeMap, map } from 'rxjs/operators'; 5 | 6 | export interface Service { 7 | name: string; 8 | instance: ChildProcess; 9 | port?: number; 10 | } 11 | 12 | export const getService = ( 13 | services: { [serviceName: string]: Service }, 14 | serviceName: string, 15 | channel: EventEmitter 16 | ) => { 17 | return new Observable((observer: Observer) => { 18 | if (services[serviceName]) { 19 | observer.next(services[serviceName]); 20 | observer.complete(); 21 | return; 22 | } 23 | 24 | const service = { 25 | name: serviceName, 26 | instance: fork(`dist/apps/${serviceName}/main`) 27 | }; 28 | 29 | channel.emit('service-started', service); 30 | 31 | service.instance.on('exit', () => { 32 | console.log(`${serviceName} has been cleared from gateway`); 33 | channel.emit('service-exitted', serviceName); 34 | }); 35 | 36 | observer.next(service); 37 | observer.complete(); 38 | }); 39 | }; 40 | 41 | export const getServicePort = (service: Service, channel: EventEmitter) => { 42 | return new Observable((observer: Observer) => { 43 | if (service.port) { 44 | observer.next(service.port); 45 | observer.complete(); 46 | return; 47 | } 48 | 49 | service.instance.on('message', ({ port }) => { 50 | channel.emit('add-port', service.name, port); 51 | observer.next(port); 52 | observer.complete(); 53 | }); 54 | }); 55 | }; 56 | 57 | export const getUrl = ( 58 | hostName: string, 59 | port: number, 60 | url: string, 61 | ssl = false 62 | ) => { 63 | const protocol = ssl ? 'https' : 'http'; 64 | return `${protocol}://${hostName}:${port}/${url}`; 65 | }; 66 | 67 | export const getServiceUrl = ( 68 | services: { [serviceName: string]: Service }, 69 | serviceName: string, 70 | originalUrl: string, 71 | channel: EventEmitter 72 | ) => { 73 | return getService(services, serviceName, channel).pipe( 74 | mergeMap((service: Service) => getServicePort(service, channel)), 75 | map((port: number) => getUrl('localhost', port, originalUrl)) 76 | ); 77 | }; 78 | 79 | export const getOriginalUrl = (url: string) => { 80 | const urlAsArray = url.split('/'); 81 | urlAsArray.splice(0, 2); 82 | return urlAsArray.join('/'); 83 | }; 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-org", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "nx": "nx", 8 | "start": "ng serve", 9 | "build": "ng build", 10 | "test": "ng test", 11 | "lint": "nx workspace-lint && ng lint", 12 | "e2e": "ng e2e", 13 | "affected:apps": "nx affected:apps", 14 | "affected:libs": "nx affected:libs", 15 | "affected:build": "nx affected:build", 16 | "affected:e2e": "nx affected:e2e", 17 | "affected:test": "nx affected:test", 18 | "affected:lint": "nx affected:lint", 19 | "affected:dep-graph": "nx affected:dep-graph", 20 | "affected": "nx affected", 21 | "format": "nx format:write", 22 | "format:write": "nx format:write", 23 | "format:check": "nx format:check", 24 | "update": "ng update @nrwl/workspace", 25 | "update:check": "ng update", 26 | "workspace-schematic": "nx workspace-schematic", 27 | "dep-graph": "nx dep-graph", 28 | "help": "nx help" 29 | }, 30 | "private": true, 31 | "dependencies": { 32 | "@nrwl/angular": "8.9.0", 33 | "@angular/animations": "^8.2.0", 34 | "@angular/common": "^8.2.0", 35 | "@angular/compiler": "^8.2.0", 36 | "@angular/core": "^8.2.0", 37 | "@angular/forms": "^8.2.0", 38 | "@angular/platform-browser": "^8.2.0", 39 | "@angular/platform-browser-dynamic": "^8.2.0", 40 | "@angular/router": "^8.2.0", 41 | "core-js": "^2.5.4", 42 | "rxjs": "~6.4.0", 43 | "zone.js": "^0.9.1", 44 | "@nestjs/common": "^6.8.3", 45 | "@nestjs/core": "^6.8.3", 46 | "@nestjs/platform-express": "^6.8.3", 47 | "reflect-metadata": "^0.1.13" 48 | }, 49 | "devDependencies": { 50 | "@angular/cli": "8.3.14", 51 | "@nrwl/workspace": "8.9.0", 52 | "@types/node": "~8.9.4", 53 | "dotenv": "6.2.0", 54 | "ts-node": "~7.0.0", 55 | "tslint": "~5.11.0", 56 | "eslint": "6.1.0", 57 | "typescript": "~3.5.3", 58 | "prettier": "1.18.2", 59 | "@nrwl/nest": "8.9.0", 60 | "@angular/compiler-cli": "^8.2.0", 61 | "@angular/language-service": "^8.2.0", 62 | "@angular-devkit/build-angular": "^0.803.14", 63 | "codelyzer": "~5.0.1", 64 | "jest-preset-angular": "7.0.0", 65 | "@nrwl/jest": "8.9.0", 66 | "jest": "24.1.0", 67 | "@types/jest": "24.0.9", 68 | "ts-jest": "24.0.0", 69 | "cypress": "3.6.1", 70 | "@nrwl/cypress": "8.9.0", 71 | "@nrwl/node": "8.9.0", 72 | "@nestjs/schematics": "^6.7.0", 73 | "@nestjs/testing": "^6.8.3" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/@nrwl/workspace/src/tslint", 4 | "node_modules/codelyzer" 5 | ], 6 | "rules": { 7 | "arrow-return-shorthand": true, 8 | "callable-types": true, 9 | "class-name": true, 10 | "deprecation": { 11 | "severity": "warn" 12 | }, 13 | "forin": true, 14 | "import-blacklist": [true, "rxjs/Rx"], 15 | "interface-over-type-literal": true, 16 | "member-access": false, 17 | "member-ordering": [ 18 | true, 19 | { 20 | "order": [ 21 | "static-field", 22 | "instance-field", 23 | "static-method", 24 | "instance-method" 25 | ] 26 | } 27 | ], 28 | "no-arg": true, 29 | "no-bitwise": true, 30 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 31 | "no-construct": true, 32 | "no-debugger": true, 33 | "no-duplicate-super": true, 34 | "no-empty": false, 35 | "no-empty-interface": true, 36 | "no-eval": true, 37 | "no-inferrable-types": [true, "ignore-params"], 38 | "no-misused-new": true, 39 | "no-non-null-assertion": true, 40 | "no-shadowed-variable": true, 41 | "no-string-literal": false, 42 | "no-string-throw": true, 43 | "no-switch-case-fall-through": true, 44 | "no-unnecessary-initializer": true, 45 | "no-unused-expression": true, 46 | "no-var-keyword": true, 47 | "object-literal-sort-keys": false, 48 | "prefer-const": true, 49 | "radix": true, 50 | "triple-equals": [true, "allow-null-check"], 51 | "unified-signatures": true, 52 | "variable-name": false, 53 | "nx-enforce-module-boundaries": [ 54 | true, 55 | { 56 | "allow": [], 57 | "depConstraints": [ 58 | { 59 | "sourceTag": "*", 60 | "onlyDependOnLibsWithTags": ["*"] 61 | } 62 | ] 63 | } 64 | ], 65 | "directive-selector": [true, "attribute", "app", "camelCase"], 66 | "component-selector": [true, "element", "app", "kebab-case"], 67 | "no-conflicting-lifecycle": true, 68 | "no-host-metadata-property": true, 69 | "no-input-rename": true, 70 | "no-inputs-metadata-property": true, 71 | "no-output-native": true, 72 | "no-output-on-prefix": true, 73 | "no-output-rename": true, 74 | "no-outputs-metadata-property": true, 75 | "template-banana-in-box": true, 76 | "template-no-negated-async": true, 77 | "use-lifecycle-interface": true, 78 | "use-pipe-transform-interface": true 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /apps/api/src/app/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { ComposedMessage, Message } from '@my-org/api-interfaces'; 2 | import { 3 | getOriginalUrl, 4 | getServiceUrl, 5 | Service 6 | } from '@my-org/service-manager'; 7 | import { 8 | Controller, 9 | Get, 10 | HttpException, 11 | HttpService, 12 | OnModuleInit, 13 | Param, 14 | Req 15 | } from '@nestjs/common'; 16 | import { AxiosResponse } from 'axios'; 17 | import { EventEmitter } from 'events'; 18 | import { Observable, throwError, zip } from 'rxjs'; 19 | import { catchError, map, mergeMap } from 'rxjs/operators'; 20 | 21 | const AVAILABLE_SERVICES = ['service-a', 'service-b']; 22 | @Controller() 23 | export class AppController implements OnModuleInit { 24 | services: { [serviceName: string]: Service } = {}; 25 | channel = new EventEmitter(); 26 | 27 | constructor(private readonly httpService: HttpService) {} 28 | 29 | onModuleInit() { 30 | this.channel.on('service-started', (service: Service) => { 31 | this.services[service.name] = service; 32 | }); 33 | 34 | this.channel.on('service-exitted', (serviceName: string) => { 35 | delete this.services[serviceName]; 36 | }); 37 | 38 | this.channel.on('add-port', (serviceName: string, port: number) => { 39 | this.services[serviceName].port = port; 40 | }); 41 | } 42 | 43 | @Get('composed') 44 | getComposedData(): Observable<{ 45 | [serviceName: string]: AxiosResponse; 46 | }> { 47 | return zip( 48 | getServiceUrl(this.services, 'service-a', 'hello', this.channel).pipe( 49 | mergeMap((url: string) => 50 | this.httpService.get(url).pipe(map(res => res.data.message)) 51 | ) 52 | ), 53 | getServiceUrl(this.services, 'service-b', 'hello', this.channel).pipe( 54 | mergeMap((url: string) => 55 | this.httpService.get(url).pipe(map(res => res.data.message)) 56 | ) 57 | ) 58 | ).pipe(map(([serviceAUrl, serviceBUrl]) => ({ serviceAUrl, serviceBUrl }))); 59 | } 60 | 61 | @Get(':serviceName/*') 62 | getData( 63 | @Req() req: Request, 64 | @Param() { serviceName }: { serviceName: string } 65 | ): Observable> { 66 | if (AVAILABLE_SERVICES.indexOf(serviceName) === -1) { 67 | throw new HttpException('Service doesnt exist', 404); 68 | } 69 | 70 | return getServiceUrl( 71 | this.services, 72 | serviceName, 73 | getOriginalUrl(req.url), 74 | this.channel 75 | ).pipe( 76 | mergeMap((url: string) => 77 | this.httpService.get(url).pipe( 78 | map(res => res.data), 79 | catchError(e => 80 | throwError(new HttpException(e.response.data, e.response.status)) 81 | ) 82 | ) 83 | ) 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /apps/my-app/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Remove template code below 3 | */ 4 | :host { 5 | display: block; 6 | font-family: sans-serif; 7 | min-width: 300px; 8 | max-width: 600px; 9 | margin: 50px auto; 10 | } 11 | 12 | .gutter-left { 13 | margin-left: 9px; 14 | } 15 | 16 | .col-span-2 { 17 | grid-column: span 2; 18 | } 19 | 20 | .flex { 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | } 25 | 26 | header { 27 | background-color: #143055; 28 | color: white; 29 | padding: 5px; 30 | border-radius: 3px; 31 | } 32 | 33 | main { 34 | padding: 0 36px; 35 | } 36 | 37 | p { 38 | text-align: center; 39 | } 40 | 41 | h1 { 42 | text-align: center; 43 | margin-left: 18px; 44 | font-size: 24px; 45 | } 46 | 47 | h2 { 48 | text-align: center; 49 | font-size: 20px; 50 | margin: 40px 0 10px 0; 51 | } 52 | 53 | .resources { 54 | text-align: center; 55 | list-style: none; 56 | padding: 0; 57 | display: grid; 58 | grid-gap: 9px; 59 | grid-template-columns: 1fr 1fr; 60 | } 61 | 62 | .resource { 63 | color: #0094ba; 64 | height: 36px; 65 | background-color: rgba(0, 0, 0, 0); 66 | border: 1px solid rgba(0, 0, 0, 0.12); 67 | border-radius: 4px; 68 | padding: 3px 9px; 69 | text-decoration: none; 70 | } 71 | 72 | .resource:hover { 73 | background-color: rgba(68, 138, 255, 0.04); 74 | } 75 | 76 | pre { 77 | padding: 9px; 78 | border-radius: 4px; 79 | background-color: black; 80 | color: #eee; 81 | } 82 | 83 | details { 84 | border-radius: 4px; 85 | color: #333; 86 | background-color: rgba(0, 0, 0, 0); 87 | border: 1px solid rgba(0, 0, 0, 0.12); 88 | padding: 3px 9px; 89 | margin-bottom: 9px; 90 | } 91 | 92 | summary { 93 | cursor: pointer; 94 | outline: none; 95 | height: 36px; 96 | line-height: 36px; 97 | } 98 | 99 | .github-star-container { 100 | margin-top: 12px; 101 | line-height: 20px; 102 | } 103 | 104 | .github-star-container a { 105 | display: flex; 106 | align-items: center; 107 | text-decoration: none; 108 | color: #333; 109 | } 110 | 111 | .github-star-badge { 112 | color: #24292e; 113 | display: flex; 114 | align-items: center; 115 | font-size: 12px; 116 | padding: 3px 10px; 117 | border: 1px solid rgba(27, 31, 35, 0.2); 118 | border-radius: 3px; 119 | background-image: linear-gradient(-180deg, #fafbfc, #eff3f6 90%); 120 | margin-left: 4px; 121 | font-weight: 600; 122 | } 123 | 124 | .github-star-badge:hover { 125 | background-image: linear-gradient(-180deg, #f0f3f6, #e6ebf1 90%); 126 | border-color: rgba(27, 31, 35, 0.35); 127 | background-position: -0.5em; 128 | } 129 | .github-star-badge .material-icons { 130 | height: 16px; 131 | width: 16px; 132 | margin-right: 4px; 133 | } 134 | -------------------------------------------------------------------------------- /apps/my-app/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microservices: API Gateways and Service Discovery with Nx and NestJs 2 | 3 | This article intention is to give you a broader perspective into the Microservices architecture. There's many people out there claiming they have a Microservice oriented architecture but they lack of the core concepts on which this pattern relies. My goal is to write a set of articles looking to clear all the fog that appears when shifting from monolithic to highly distributed application. 4 | 5 | The Microservices world is full of interesting and incredibly hard to implement stuff. When you get started you think that by just dividing your app in multiple services you are already there. Sadly, that's almost never true. It's more common than you think to see people building highly critical apps in this way without having in place all the core concepts. 6 | 7 | In this article I'm going to focus in two patterns API Gateway and Service Discovery. If you are doing Microservice architecture you **SHOULD** know these two pretty well, being that the case use this article to make sure you have clear knowledge on these concepts. If you are enterily new to Microservices, have fun and enjoy the ride. 8 | 9 | In traditional monolithic applications, API clients consume everything from the same location. Although, once you start using microservices things start to change, you may have multiple services running on entirely different locations. Typically those locations aren't static so that has to be taken into account. 10 | 11 | ## What API Gateway means 12 | 13 | The non deterministic nature of microservice architecture lead us directly to whole new mess. But what can you do about it? One of the approaches out there is the API Gateway. From a 10,000ft view it's just an extra service that you put in front of your other services so you can do composition of services. 14 | 15 | ## What Service Discovery means 16 | 17 | Imagine you have multiple distributed services which can be running (or NOT). You don't want to manually handle all those services, instead is better if you have some code to do these: 18 | 19 | - Verify if there's an instance of the service running 20 | - If there's no instance of the service running 21 | - Start a new instance of the service 22 | - Return the service location 23 | 24 | You are basically setting everything so your Service Discovery implementation is the responsible of managing the services. If you want to implement your own Load Balancer, this will put you into the right direction. 25 | 26 | ## The problem you are going to solve 27 | 28 | Let's say you have an application that consists on multiple services, each of these running in the same physical server under different ports. We want to have our services location hidden from clients, so we'll have a proxy service that has to be able to compose multiple requests. It has to be capable of instatiating other services when needed and keeping track of their locations and status. 29 | 30 | > NOTE: All the services have a heart beat mechanism built in, so if they spend too much time idle they get shutted down. We can't forget to wipe that from the available services. 31 | 32 | ## Solution 33 | 34 | ### How the services work 35 | 36 | Let's analyse how the services work. A Service has a single endpoint that can be accessed through `http://localhost:${port}/hello`. Once they start: 37 | 38 | - Get assigned a random available port 39 | - Send the port through inter process communication 40 | - Start a heart beat mechanism 41 | 42 | If you are wondering what I mean by _heart beat mechanism_, is simply a class that receives a serviceName and a configuration object. Once the heartBeat instance is initiated it will start an interval and will check with a predefined frequency if the service has reached the maximum time idle. 43 | 44 | > NOTE: The frequency of the interval and the maximum time idle are part of the configuration and can be modified if needed. 45 | 46 | Once the service reaches the maximum time idle, the entire process is killed. 47 | 48 | ### How the API works 49 | 50 | Since our services run in the same server, from the API service we can start a new service instance. The API service has to keep track of the services location and status. Clients will perform requests to the API service and it will take care of redirecting and composing service consumption. 51 | 52 | ## Implementation 53 | 54 | All I've said sounds nice right? But before continuing I highly recommend you to take a step back and think. 55 | 56 | > How would I solve it? 57 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "projects": { 4 | "my-app": { 5 | "projectType": "application", 6 | "schematics": { 7 | "@nrwl/angular:component": { 8 | "style": "scss" 9 | } 10 | }, 11 | "root": "apps/my-app", 12 | "sourceRoot": "apps/my-app/src", 13 | "prefix": "my-org", 14 | "architect": { 15 | "build": { 16 | "builder": "@angular-devkit/build-angular:browser", 17 | "options": { 18 | "outputPath": "dist/apps/my-app", 19 | "index": "apps/my-app/src/index.html", 20 | "main": "apps/my-app/src/main.ts", 21 | "polyfills": "apps/my-app/src/polyfills.ts", 22 | "tsConfig": "apps/my-app/tsconfig.app.json", 23 | "aot": false, 24 | "assets": ["apps/my-app/src/favicon.ico", "apps/my-app/src/assets"], 25 | "styles": ["apps/my-app/src/styles.scss"], 26 | "scripts": [] 27 | }, 28 | "configurations": { 29 | "production": { 30 | "fileReplacements": [ 31 | { 32 | "replace": "apps/my-app/src/environments/environment.ts", 33 | "with": "apps/my-app/src/environments/environment.prod.ts" 34 | } 35 | ], 36 | "optimization": true, 37 | "outputHashing": "all", 38 | "sourceMap": false, 39 | "extractCss": true, 40 | "namedChunks": false, 41 | "aot": true, 42 | "extractLicenses": true, 43 | "vendorChunk": false, 44 | "buildOptimizer": true, 45 | "budgets": [ 46 | { 47 | "type": "initial", 48 | "maximumWarning": "2mb", 49 | "maximumError": "5mb" 50 | }, 51 | { 52 | "type": "anyComponentStyle", 53 | "maximumWarning": "6kb", 54 | "maximumError": "10kb" 55 | } 56 | ] 57 | } 58 | } 59 | }, 60 | "serve": { 61 | "builder": "@angular-devkit/build-angular:dev-server", 62 | "options": { 63 | "browserTarget": "my-app:build", 64 | "proxyConfig": "apps/my-app/proxy.conf.json" 65 | }, 66 | "configurations": { 67 | "production": { 68 | "browserTarget": "my-app:build:production" 69 | } 70 | } 71 | }, 72 | "extract-i18n": { 73 | "builder": "@angular-devkit/build-angular:extract-i18n", 74 | "options": { 75 | "browserTarget": "my-app:build" 76 | } 77 | }, 78 | "lint": { 79 | "builder": "@angular-devkit/build-angular:tslint", 80 | "options": { 81 | "tsConfig": [ 82 | "apps/my-app/tsconfig.app.json", 83 | "apps/my-app/tsconfig.spec.json" 84 | ], 85 | "exclude": ["**/node_modules/**", "!apps/my-app/**"] 86 | } 87 | }, 88 | "test": { 89 | "builder": "@nrwl/jest:jest", 90 | "options": { 91 | "jestConfig": "apps/my-app/jest.config.js", 92 | "tsConfig": "apps/my-app/tsconfig.spec.json", 93 | "setupFile": "apps/my-app/src/test-setup.ts" 94 | } 95 | } 96 | } 97 | }, 98 | "my-app-e2e": { 99 | "root": "apps/my-app-e2e", 100 | "sourceRoot": "apps/my-app-e2e/src", 101 | "projectType": "application", 102 | "architect": { 103 | "e2e": { 104 | "builder": "@nrwl/cypress:cypress", 105 | "options": { 106 | "cypressConfig": "apps/my-app-e2e/cypress.json", 107 | "tsConfig": "apps/my-app-e2e/tsconfig.e2e.json", 108 | "devServerTarget": "my-app:serve" 109 | }, 110 | "configurations": { 111 | "production": { 112 | "devServerTarget": "my-app:serve:production" 113 | } 114 | } 115 | }, 116 | "lint": { 117 | "builder": "@angular-devkit/build-angular:tslint", 118 | "options": { 119 | "tsConfig": ["apps/my-app-e2e/tsconfig.e2e.json"], 120 | "exclude": ["**/node_modules/**", "!apps/my-app-e2e/**"] 121 | } 122 | } 123 | } 124 | }, 125 | "api": { 126 | "root": "apps/api", 127 | "sourceRoot": "apps/api/src", 128 | "projectType": "application", 129 | "prefix": "api", 130 | "schematics": {}, 131 | "architect": { 132 | "build": { 133 | "builder": "@nrwl/node:build", 134 | "options": { 135 | "outputPath": "dist/apps/api", 136 | "main": "apps/api/src/main.ts", 137 | "tsConfig": "apps/api/tsconfig.app.json", 138 | "assets": ["apps/api/src/assets"] 139 | }, 140 | "configurations": { 141 | "production": { 142 | "optimization": true, 143 | "extractLicenses": true, 144 | "inspect": false, 145 | "fileReplacements": [ 146 | { 147 | "replace": "apps/api/src/environments/environment.ts", 148 | "with": "apps/api/src/environments/environment.prod.ts" 149 | } 150 | ] 151 | } 152 | } 153 | }, 154 | "serve": { 155 | "builder": "@nrwl/node:execute", 156 | "options": { 157 | "buildTarget": "api:build", 158 | "port": 0 159 | } 160 | }, 161 | "lint": { 162 | "builder": "@angular-devkit/build-angular:tslint", 163 | "options": { 164 | "tsConfig": [ 165 | "apps/api/tsconfig.app.json", 166 | "apps/api/tsconfig.spec.json" 167 | ], 168 | "exclude": ["**/node_modules/**", "!apps/api/**"] 169 | } 170 | }, 171 | "test": { 172 | "builder": "@nrwl/jest:jest", 173 | "options": { 174 | "jestConfig": "apps/api/jest.config.js", 175 | "tsConfig": "apps/api/tsconfig.spec.json" 176 | } 177 | } 178 | } 179 | }, 180 | "api-interfaces": { 181 | "root": "libs/api-interfaces", 182 | "sourceRoot": "libs/api-interfaces/src", 183 | "projectType": "library", 184 | "schematics": {}, 185 | "architect": { 186 | "lint": { 187 | "builder": "@angular-devkit/build-angular:tslint", 188 | "options": { 189 | "tsConfig": [ 190 | "libs/api-interfaces/tsconfig.lib.json", 191 | "libs/api-interfaces/tsconfig.spec.json" 192 | ], 193 | "exclude": ["**/node_modules/**", "!libs/api-interfaces/**"] 194 | } 195 | }, 196 | "test": { 197 | "builder": "@nrwl/jest:jest", 198 | "options": { 199 | "jestConfig": "libs/api-interfaces/jest.config.js", 200 | "tsConfig": "libs/api-interfaces/tsconfig.spec.json" 201 | } 202 | } 203 | } 204 | }, 205 | "service-a": { 206 | "root": "apps/service-a", 207 | "sourceRoot": "apps/service-a/src", 208 | "projectType": "application", 209 | "prefix": "service-a", 210 | "schematics": {}, 211 | "architect": { 212 | "build": { 213 | "builder": "@nrwl/node:build", 214 | "options": { 215 | "outputPath": "dist/apps/service-a", 216 | "main": "apps/service-a/src/main.ts", 217 | "tsConfig": "apps/service-a/tsconfig.app.json", 218 | "assets": ["apps/service-a/src/assets"] 219 | }, 220 | "configurations": { 221 | "production": { 222 | "optimization": true, 223 | "extractLicenses": true, 224 | "inspect": false, 225 | "fileReplacements": [ 226 | { 227 | "replace": "apps/service-a/src/environments/environment.ts", 228 | "with": "apps/service-a/src/environments/environment.prod.ts" 229 | } 230 | ] 231 | } 232 | } 233 | }, 234 | "serve": { 235 | "builder": "@nrwl/node:execute", 236 | "options": { 237 | "buildTarget": "service-a:build", 238 | "port": 0 239 | } 240 | }, 241 | "lint": { 242 | "builder": "@angular-devkit/build-angular:tslint", 243 | "options": { 244 | "tsConfig": [ 245 | "apps/service-a/tsconfig.app.json", 246 | "apps/service-a/tsconfig.spec.json" 247 | ], 248 | "exclude": ["**/node_modules/**", "!apps/service-a/**"] 249 | } 250 | }, 251 | "test": { 252 | "builder": "@nrwl/jest:jest", 253 | "options": { 254 | "jestConfig": "apps/service-a/jest.config.js", 255 | "tsConfig": "apps/service-a/tsconfig.spec.json" 256 | } 257 | } 258 | } 259 | }, 260 | "service-b": { 261 | "root": "apps/service-b", 262 | "sourceRoot": "apps/service-b/src", 263 | "projectType": "application", 264 | "prefix": "service-b", 265 | "schematics": {}, 266 | "architect": { 267 | "build": { 268 | "builder": "@nrwl/node:build", 269 | "options": { 270 | "outputPath": "dist/apps/service-b", 271 | "main": "apps/service-b/src/main.ts", 272 | "tsConfig": "apps/service-b/tsconfig.app.json", 273 | "assets": ["apps/service-b/src/assets"] 274 | }, 275 | "configurations": { 276 | "production": { 277 | "optimization": true, 278 | "extractLicenses": true, 279 | "inspect": false, 280 | "fileReplacements": [ 281 | { 282 | "replace": "apps/service-b/src/environments/environment.ts", 283 | "with": "apps/service-b/src/environments/environment.prod.ts" 284 | } 285 | ] 286 | } 287 | } 288 | }, 289 | "serve": { 290 | "builder": "@nrwl/node:execute", 291 | "options": { 292 | "buildTarget": "service-b:build", 293 | "port": 0 294 | } 295 | }, 296 | "lint": { 297 | "builder": "@angular-devkit/build-angular:tslint", 298 | "options": { 299 | "tsConfig": [ 300 | "apps/service-b/tsconfig.app.json", 301 | "apps/service-b/tsconfig.spec.json" 302 | ], 303 | "exclude": ["**/node_modules/**", "!apps/service-b/**"] 304 | } 305 | }, 306 | "test": { 307 | "builder": "@nrwl/jest:jest", 308 | "options": { 309 | "jestConfig": "apps/service-b/jest.config.js", 310 | "tsConfig": "apps/service-b/tsconfig.spec.json" 311 | } 312 | } 313 | } 314 | }, 315 | "heart-beat": { 316 | "root": "libs/heart-beat", 317 | "sourceRoot": "libs/heart-beat/src", 318 | "projectType": "library", 319 | "schematics": {}, 320 | "architect": { 321 | "lint": { 322 | "builder": "@angular-devkit/build-angular:tslint", 323 | "options": { 324 | "tsConfig": [ 325 | "libs/heart-beat/tsconfig.lib.json", 326 | "libs/heart-beat/tsconfig.spec.json" 327 | ], 328 | "exclude": ["**/node_modules/**", "!libs/heart-beat/**"] 329 | } 330 | }, 331 | "test": { 332 | "builder": "@nrwl/jest:jest", 333 | "options": { 334 | "jestConfig": "libs/heart-beat/jest.config.js", 335 | "tsConfig": "libs/heart-beat/tsconfig.spec.json" 336 | } 337 | } 338 | } 339 | }, 340 | "service-manager": { 341 | "root": "libs/service-manager", 342 | "sourceRoot": "libs/service-manager/src", 343 | "projectType": "library", 344 | "schematics": {}, 345 | "architect": { 346 | "lint": { 347 | "builder": "@angular-devkit/build-angular:tslint", 348 | "options": { 349 | "tsConfig": [ 350 | "libs/service-manager/tsconfig.lib.json", 351 | "libs/service-manager/tsconfig.spec.json" 352 | ], 353 | "exclude": ["**/node_modules/**", "!libs/service-manager/**"] 354 | } 355 | }, 356 | "test": { 357 | "builder": "@nrwl/jest:jest", 358 | "options": { 359 | "jestConfig": "libs/service-manager/jest.config.js", 360 | "tsConfig": "libs/service-manager/tsconfig.spec.json" 361 | } 362 | } 363 | } 364 | } 365 | }, 366 | "cli": { 367 | "defaultCollection": "@nrwl/angular" 368 | }, 369 | "schematics": { 370 | "@nrwl/angular:application": { 371 | "unitTestRunner": "jest", 372 | "e2eTestRunner": "cypress" 373 | }, 374 | "@nrwl/angular:library": { 375 | "unitTestRunner": "jest" 376 | } 377 | }, 378 | "defaultProject": "my-app" 379 | } 380 | --------------------------------------------------------------------------------