├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── doc ├── common.http └── users.http ├── jest.config.json ├── nest-cli.json ├── nodemon-debug.json ├── nodemon.json ├── package-lock.json ├── package.json ├── src ├── app.module.ts ├── application │ ├── __test__ │ │ └── hello.controller.spec.ts │ ├── controllers │ │ └── hello.controller.ts │ ├── dto │ │ └── create-user.dto.ts │ ├── interceptors │ │ └── logging.interceptor.ts │ └── middlewere │ │ └── logger.middleware.ts ├── constants.ts ├── domain │ ├── __test__ │ │ └── user.service.spec.ts │ ├── entities │ │ └── User.ts │ └── services │ │ ├── logger.service.ts │ │ └── user.service.ts ├── infrastructure │ ├── database │ │ ├── database.module.ts │ │ └── database.providers.ts │ ├── health │ │ └── terminus-options.check.ts │ ├── models │ │ ├── index.ts │ │ └── user.model.ts │ └── repository │ │ └── user.repository.ts └── main.ts ├── test ├── app.e2e-spec.ts └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | .vscode 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json 36 | 37 | # Env 38 | .env 39 | *.sqlite -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # NestJs Clean Arquitecture 3 | Mutation test - stiker 4 | This is an example of using DDD with nestjs, only for proof of concept and framework study 5 | If you want more documentation of it, click here [Nest](https://github.com/nestjs/nest) 6 | 7 | [A quick introduction to clean architecture](https://www.freecodecamp.org/news/a-quick-introduction-to-clean-architecture-990c014448d2/) 8 | 9 | ![alt text](https://cdn-media-1.freecodecamp.org/images/oVVbTLR5gXHgP8Ehlz1qzRm5LLjX9kv2Zri6) 10 | 11 | 12 | 13 | ## Branch Definitions 14 | * **master**: Flat structure with mongo connection 15 | * **flat-structure-sql**: Flat structure with connection to sqlite can be easily changed to any connection that allows sequelizejs 16 | * **hexagonal-architecture**: Exagonal structure with mongo connection 17 | 18 | ## Getting Started 19 | 20 | ``` 21 | git clone git@github.com:ecaminero/nestjs-ddd.git 22 | ``` 23 | 24 | ### Structure 25 | ``` 26 | . 27 | ├── doc 28 | │   ├── *.http 29 | ├── src 30 | │   ├── app 31 | │   │   ├── __test__ 32 | │   │   │   └── *.controller.spec.ts 33 | │   │   ├── controller 34 | │   │   │   └── *.controller.ts 35 | │   │   ├── dto 36 | │   │   │   └── *.dto.ts 37 | │   │   ├── interceptors 38 | │   │   │   └── *.interceptor.ts 39 | │   │   └── middlewere 40 | │   │   └── *.middleware.ts 41 | │   ├── domain 42 | │   │   ├── __test__ 43 | │   │   │   └── *.service.spec.ts 44 | │   │   ├── entities 45 | │   │   │   └── *.entity.ts 46 | │   │   └── service 47 | │   │   └── *.service.ts 48 | │   ├── infrastructure 49 | │   │   ├── database 50 | │   │   │   └── *.providers.ts 51 | │   │   ├── health 52 | │   │   │   └── *.check.ts 53 | │   │   ├── model 54 | │   │   │   └── *.model.ts 55 | │   │   └── repository 56 | │   │   └── *.repository.ts 57 | │   ├── main.ts 58 | │   ├── app.module.ts 59 | │   ├── constants.ts 60 | ├── test 61 | │   ├── app.e2e-spec.ts 62 | │   └── jest-e2e.json 63 | ├── jest.config.json 64 | ├── localhost.sqlite 65 | ├── nest-cli.json 66 | ├── nodemon-debug.json 67 | ├── nodemon.json 68 | ├── package-lock.json 69 | ├── package.json 70 | ├── README.md 71 | ├── tsconfig.build.json 72 | ├── tsconfig.json 73 | └── tslint.json 74 | 75 | ``` 76 | 77 | ### Prerequisites 78 | 79 | * node 10+ 80 | * mongo DB 81 | * Sqlite 82 | 83 | ```bash 84 | $ docker run --name dev-mongo -p 27017:27017 -d mongo 85 | ``` 86 | 87 | ### Installing 88 | 89 | ```bash 90 | $ npm install 91 | ``` 92 | 93 | If you see this everything all fine 94 | ``` 95 | 96 | added 898 packages from 578 contributors and audited 876746 packages in 11.087s 97 | found 0 vulnerabilities 98 | 99 | ``` 100 | 101 | ## Running the tests 102 | 103 | ```bash 104 | # unit tests 105 | $ npm run test 106 | 107 | # e2e tests 108 | $ npm run test:e2e 109 | 110 | # coverage 111 | $ npm run test:cov 112 | 113 | ``` 114 | 115 | 116 | ## Built With 117 | 118 | * [Nest](https://github.com/nestjs/nest) - The framework used 119 | 120 | * [Mongoosejs](https://mongoosejs.com/) - elegant object modeling for Nodejs 121 | 122 | * [node.js](https://nodejs.org/en/)- Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine 123 | 124 | * [jestjs](https://jestjs.io/en/) Jest is a delightful JavaScript Testing Framework with a focus on simplicity 125 | 126 | * [sequelizejs](http://docs.sequelizejs.com/) Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more. 127 | 128 | 129 | ## Authors 130 | 131 | * **Edwin Caminero** - *Initial work* - [github](https://github.com/ecaminero) 132 | 133 | See also the list of [contributors](https://github.com/ecaminero/nestjs-ddd/contributors) who participated in this project. 134 | 135 | 136 | ## License 137 | 138 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 139 | 140 | 141 | ## Acknowledgments 142 | 143 | * Inspiration 144 | * Hexagonal architecture concept tests 145 | -------------------------------------------------------------------------------- /doc/common.http: -------------------------------------------------------------------------------- 1 | @hostname = localhost 2 | @port = 4000 3 | @host = {{hostname}}:{{port}} 4 | @contentType = application/json 5 | 6 | ### Create Uuser 7 | GET http://{{host}}/hello HTTP/1.1 8 | 9 | 10 | ### health check 11 | GET http://{{host}}/health HTTP/1.1 12 | -------------------------------------------------------------------------------- /doc/users.http: -------------------------------------------------------------------------------- 1 | @hostname = localhost 2 | @port = 4000 3 | @host = {{hostname}}:{{port}} 4 | @contentType = application/json 5 | 6 | ### Create Uuser 7 | POST http://{{host}} HTTP/1.1 8 | Content-Type: {{contentType}} 9 | 10 | { 11 | "name" : "Mertie Beier Sr.", 12 | "lastname" : "Ziemann", 13 | "age" : 90402 14 | } 15 | 16 | 17 | ### Create Uuser 18 | GET http://{{host}}/all HTTP/1.1 19 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "coveragePathIgnorePatterns": [ 3 | "(.test)\\.(ts|tsx|js)$" 4 | ] 5 | } -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "ts", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /nodemon-debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "node --inspect-brk -r ts-node/register -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["dist"], 3 | "ext": "js", 4 | "exec": "node dist/main" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-ddd", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "tsc -p tsconfig.build.json", 9 | "format": "prettier --write \"src/**/*.ts\"", 10 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 11 | "start:dev": "concurrently --handle-input \"wait-on dist/main.js && nodemon\" \"tsc -w -p tsconfig.build.json\" ", 12 | "start:debug": "nodemon --config nodemon-debug.json", 13 | "prestart:prod": "rimraf dist && npm run build", 14 | "start:prod": "node dist/main.js", 15 | "lint": "tslint -p tsconfig.json -c tslint.json", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage ", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@godaddy/terminus": "^4.1.2", 24 | "@nestjs/common": "^6.5.2", 25 | "@nestjs/core": "^6.5.2", 26 | "@nestjs/mongoose": "^6.1.2", 27 | "@nestjs/platform-express": "^6.5.2", 28 | "@nestjs/terminus": "^6.5.0", 29 | "@types/uuid": "^3.4.5", 30 | "class-transformer": "^0.2.3", 31 | "class-validator": "^0.9.1", 32 | "dotenv": "^8.0.0", 33 | "lodash": "^4.17.14", 34 | "module-alias": "^2.2.1", 35 | "moment": "^2.24.0", 36 | "mongoose": "^5.6.4", 37 | "reflect-metadata": "^0.1.12", 38 | "rimraf": "^2.6.2", 39 | "rxjs": "^6.3.3" 40 | }, 41 | "devDependencies": { 42 | "@nestjs/testing": "^6.5.2", 43 | "@types/dotenv": "^6.1.1", 44 | "@types/express": "^4.16.0", 45 | "@types/faker": "^4.1.5", 46 | "@types/jest": "^23.3.13", 47 | "@types/node": "^10.12.18", 48 | "@types/supertest": "^2.0.7", 49 | "concurrently": "^4.1.0", 50 | "faker": "^4.1.0", 51 | "jest": "^24.8.0", 52 | "nodemon": "^1.18.9", 53 | "prettier": "^1.15.3", 54 | "supertest": "^3.4.1", 55 | "ts-jest": "24.0.2", 56 | "ts-node": "8.1.0", 57 | "tsconfig-paths": "3.8.0", 58 | "tslint": "5.16.0", 59 | "typescript": "3.4.3", 60 | "wait-on": "^3.3.0" 61 | }, 62 | "jest": { 63 | "coveragePathIgnorePatterns": [ 64 | "(.dto)\\.(ts|tsx|js)$", 65 | "(.interceptor)\\.(ts|tsx|js)$" 66 | ], 67 | "moduleFileExtensions": [ 68 | "js", 69 | "json", 70 | "ts" 71 | ], 72 | "rootDir": "src", 73 | "testRegex": ".spec.ts$", 74 | "transform": { 75 | "^.+\\.(t|j)s$": "ts-jest" 76 | }, 77 | "coverageDirectory": "../coverage", 78 | "testEnvironment": "node", 79 | "moduleNameMapper": { 80 | "@application(.*)$": "/application$1", 81 | "@domain(.*)$": "/domain$1", 82 | "@infrastructure(.*)$": "/infrastructure$1", 83 | "@constants(.*)$": "/constants$1" 84 | } 85 | }, 86 | "_moduleAliases": { 87 | "@root": ".", 88 | "@domain": "dist/domain/*", 89 | "@constants": "dist/constants", 90 | "@application": "dist/application/*", 91 | "@infrastructure": "dist/infrastructure/*" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module , NestModule, MiddlewareConsumer } from '@nestjs/common'; 2 | import { HelloController } from './application/controllers/hello.controller'; 3 | import { UserService } from './domain/services/user.service'; 4 | import { DatabaseModule } from './infrastructure/database/database.module'; 5 | import { modelProviders } from './infrastructure/models'; 6 | import { UserRepository } from './infrastructure/repository/user.repository'; 7 | import { LoggerMiddleware } from './application/middlewere/logger.middleware'; 8 | import { TerminusModule } from '@nestjs/terminus'; 9 | import { TerminusOptionsService } from './infrastructure/health/terminus-options.check'; 10 | 11 | const HealthModule = TerminusModule.forRootAsync({ 12 | useClass: TerminusOptionsService, 13 | }); 14 | 15 | @Module({ 16 | imports: [DatabaseModule, HealthModule], 17 | controllers: [HelloController], 18 | providers: [ 19 | UserService, 20 | UserRepository, 21 | ...modelProviders, 22 | ], 23 | }) 24 | export class AppModule implements NestModule { 25 | configure(consumer: MiddlewareConsumer) { 26 | consumer 27 | .apply(LoggerMiddleware) 28 | .forRoutes(HelloController); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/application/__test__/hello.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import * as faker from 'faker'; 2 | import { Test } from '@nestjs/testing'; 3 | import { Model } from 'mongoose'; 4 | import { has, cloneDeep } from 'lodash'; 5 | import { TestingModule } from '@nestjs/testing/testing-module'; 6 | import { HelloController } from '@application/controllers/hello.controller'; 7 | import { UserService } from '@domain/services/user.service'; 8 | import { UserModel } from '@infrastructure/models/User.model'; 9 | import { User } from '@domain/entities/User'; 10 | import { USER_MODEL_PROVIDER } from '@constants'; 11 | import { UserRepository } from '@infrastructure/repository/user.repository'; 12 | 13 | describe('User Controller', () => { 14 | let controller: HelloController; 15 | let service: UserService; 16 | const userModel: Model = UserModel; 17 | 18 | beforeAll(async () => { 19 | const userProviders = { 20 | provide: USER_MODEL_PROVIDER, 21 | useValue: userModel, 22 | }; 23 | 24 | const module: TestingModule = await Test 25 | .createTestingModule({ 26 | controllers: [HelloController], 27 | providers: [ 28 | UserService, 29 | UserRepository, 30 | userProviders, 31 | ], 32 | }) 33 | .compile(); 34 | 35 | controller = module.get(HelloController); 36 | service = module.get(UserService); 37 | }); 38 | 39 | it('should create an user', async () => { 40 | const user: User = { 41 | _id: faker.random.uuid(), 42 | name: faker.name.findName(), 43 | lastname: faker.name.lastName(), 44 | age: faker.random.number(), 45 | }; 46 | const newUser = cloneDeep(user); 47 | jest.spyOn(service, 'create').mockImplementation(async () => user); 48 | const data = await controller.create(newUser); 49 | expect(data).toBeDefined(); 50 | expect(has(data , '_id')).toBeTruthy(); 51 | Object.keys(data).forEach((key) => { 52 | expect(data[key]).toBe(user[key]); 53 | }); 54 | }); 55 | 56 | it('should return Hello word', async () => { 57 | const data = await controller.get(); 58 | expect(data).toBeDefined(); 59 | expect(data).toBe('Hello World!'); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/application/controllers/hello.controller.ts: -------------------------------------------------------------------------------- 1 | import { LoggerService, Context } from '@domain/services/logger.service'; 2 | import { Controller, Get, Post, Body, UseInterceptors } from '@nestjs/common'; 3 | import { UserService } from '@domain/services/user.service'; 4 | import { CreateUserDto } from '@application/dto/create-user.dto'; 5 | import { User } from '@domain/entities/User'; 6 | import { LoggingInterceptor } from '@application/interceptors/logging.interceptor'; 7 | 8 | // UserController 9 | @Controller() 10 | @UseInterceptors(LoggingInterceptor) 11 | export class HelloController { 12 | private Log: LoggerService = new LoggerService('createOperation'); 13 | constructor(private readonly userService: UserService) {} 14 | 15 | @Get('/hello') 16 | get(): string { 17 | const context: Context = { module: 'HelloController', method: 'get' }; 18 | this.Log.logger('Hello World!', context); 19 | return 'Hello World!'; 20 | } 21 | 22 | @Get('/all') 23 | async getAll(): Promise { 24 | const context: Context = { module: 'HelloController', method: 'getAll' }; 25 | this.Log.logger('Hello World!', context); 26 | return await this.userService.find(); 27 | } 28 | 29 | @Post('/') 30 | async create(@Body() user: CreateUserDto): Promise { 31 | const context: Context = { module: 'HelloController', method: 'create' }; 32 | this.Log.logger(user, context); 33 | return await this.userService.create(user); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/application/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString, IsNumber} from 'class-validator'; 2 | 3 | /* istanbul ignore if */ 4 | export class CreateUserDto { 5 | @IsString() 6 | @IsNotEmpty() 7 | name: string; 8 | 9 | @IsString() 10 | @IsNotEmpty() 11 | lastname: string; 12 | 13 | @IsNumber() 14 | @IsNotEmpty() 15 | age: number; 16 | } 17 | -------------------------------------------------------------------------------- /src/application/interceptors/logging.interceptor.ts: -------------------------------------------------------------------------------- 1 | 2 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 3 | import { Observable } from 'rxjs'; 4 | import { tap } from 'rxjs/operators'; 5 | 6 | @Injectable() 7 | export class LoggingInterceptor implements NestInterceptor { 8 | intercept(context: ExecutionContext, next: CallHandler): Observable { 9 | const now = Date.now(); 10 | return next 11 | .handle() 12 | .pipe(tap(() => console.log(`Execution time... ${Date.now() - now}ms`))); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/application/middlewere/logger.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class LoggerMiddleware implements NestMiddleware { 5 | use(req: Request, res: Response, next: any) { 6 | console.log('Request...', req.body); 7 | next(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv'; 2 | dotenv.config(); 3 | 4 | export const DB_PROVIDER = 'DbConnectionToken'; 5 | export const USER_MODEL_PROVIDER = 'UserModelProvider'; 6 | export const SERVICE = 'DB_MONGO_SERVICE'; 7 | export const APP_NAME = process.env.APP_NAME || 'clean.architecture'; 8 | export const DATABASE_SERVICE = process.env.DATABASE_SERVICE || 'DATABASE_SERVICE'; 9 | export const APP_PORT = process.env.PORT || 4000; 10 | export const APP_HOST = process.env.APP_HOST || '0.0.0.0'; -------------------------------------------------------------------------------- /src/domain/__test__/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import * as faker from 'faker'; 2 | import { cloneDeep } from 'lodash'; 3 | import { Test } from '@nestjs/testing'; 4 | import { Model } from 'mongoose'; 5 | import { TestingModule } from '@nestjs/testing/testing-module'; 6 | import { USER_MODEL_PROVIDER } from '@constants'; 7 | import { UserService } from '@domain/services/user.service'; 8 | import { User } from '@domain/entities/User'; 9 | import { UserModel } from '@infrastructure/models/User.model'; 10 | import { UserRepository } from '@infrastructure/repository/user.repository'; 11 | 12 | describe('User Service', () => { 13 | let service: UserService; 14 | let userModel: Model = UserModel; 15 | let repository: UserRepository; 16 | 17 | beforeAll(async () => { 18 | userModel = UserModel; 19 | 20 | const userProviders = { 21 | provide: USER_MODEL_PROVIDER, 22 | useValue: userModel, 23 | }; 24 | 25 | const module: TestingModule = await Test 26 | .createTestingModule({ 27 | providers: [ 28 | UserService, 29 | UserRepository, 30 | userProviders, 31 | ], 32 | }) 33 | .compile(); 34 | 35 | service = module.get(UserService); 36 | repository = module.get(UserRepository); 37 | }); 38 | 39 | it('should create a user', async () => { 40 | const user = { 41 | _id: faker.random.uuid(), 42 | name: faker.name.findName(), 43 | lastname: faker.name.lastName(), 44 | age: faker.random.number(), 45 | }; 46 | 47 | const newUser = cloneDeep(user); 48 | jest.spyOn(repository, 'create').mockImplementation(async () => user); 49 | const data = await service.create(newUser); 50 | expect(data).toBeDefined(); 51 | expect(data._id).toBeDefined(); 52 | Object.keys(data).forEach((key) => { 53 | expect(data[key]).toBe(user[key]); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/domain/entities/User.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsNumber, IsString} from 'class-validator'; 2 | // tslint:disable-next-line: max-classes-per-file 3 | export class User { 4 | // tslint:disable-next-line: variable-name 5 | readonly _id?: string; 6 | 7 | @IsString() 8 | @IsNotEmpty() 9 | name: string; 10 | 11 | @IsString() 12 | @IsNotEmpty() 13 | lastname: string; 14 | 15 | @IsNumber() 16 | @IsNotEmpty() 17 | age: number; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/domain/services/logger.service.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common'; 2 | import { APP_HOST } from '@constants'; 3 | 4 | export class Context { 5 | module: string; 6 | method: string; 7 | } 8 | 9 | // tslint:disable-next-line: max-classes-per-file 10 | export class LoggerService extends Logger { 11 | logger(message: any, context?: Context) { 12 | const standard = {server: APP_HOST, type: 'INFO', time: Date.now()}; 13 | const data = {...standard, ...context, message}; 14 | super.log(data); 15 | } 16 | 17 | err(message: any, context: Context) { 18 | const standard = {server: APP_HOST, type: 'ERROR', time: Date.now()}; 19 | const data = {...standard, ...context, message}; 20 | super.error(data); 21 | } 22 | 23 | warning(message: any, context: Context) { 24 | const standard = {server: APP_HOST, type: 'WARNING', time: Date.now()}; 25 | const data = {...standard, ...context, message}; 26 | super.warn(data); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/domain/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@nestjs/common'; 2 | import { User } from '@domain/entities/User'; 3 | import { CreateUserDto } from '@application/dto/create-user.dto'; 4 | import { UserRepository } from '@infrastructure/repository/user.repository'; 5 | 6 | // Se inyecta el repo en el servicio 7 | @Injectable() 8 | export class UserService { 9 | constructor(private readonly repository: UserRepository) {} 10 | 11 | getHello(): string { 12 | return 'Hello World!'; 13 | } 14 | 15 | async create(createUserDto: CreateUserDto): Promise { 16 | return await this.repository.create(createUserDto); 17 | } 18 | 19 | async find(): Promise { 20 | return await this.repository.find(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/infrastructure/database/database.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { databaseProviders } from './database.providers'; 3 | 4 | @Module({ 5 | providers: [...databaseProviders], 6 | exports: [...databaseProviders], 7 | }) 8 | export class DatabaseModule {} 9 | -------------------------------------------------------------------------------- /src/infrastructure/database/database.providers.ts: -------------------------------------------------------------------------------- 1 | import * as mongoose from 'mongoose'; 2 | import { DB_PROVIDER } from '@constants'; 3 | 4 | export const databaseProviders = [{ 5 | provide: DB_PROVIDER, 6 | useFactory: async (): Promise => await {}, 7 | }]; 8 | -------------------------------------------------------------------------------- /src/infrastructure/health/terminus-options.check.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TerminusEndpoint, 3 | TerminusOptionsFactory, 4 | DNSHealthIndicator, 5 | TerminusModuleOptions, 6 | MemoryHealthIndicator, 7 | } from '@nestjs/terminus'; 8 | import { Injectable } from '@nestjs/common'; 9 | 10 | @Injectable() 11 | export class TerminusOptionsService implements TerminusOptionsFactory { 12 | constructor( 13 | private readonly dns: DNSHealthIndicator, 14 | private readonly memory: MemoryHealthIndicator, 15 | ) {} 16 | 17 | createTerminusOptions(): TerminusModuleOptions { 18 | const healthEndpoint: TerminusEndpoint = { 19 | url: '/health', 20 | healthIndicators: [ 21 | async () => this.dns.pingCheck('google', 'https://google.com'), 22 | async () => this.memory.checkHeap('memory_heap', 200 * 1024 * 1024), 23 | async () => this.memory.checkRSS('memory_rss', 3000 * 1024 * 1024), 24 | ], 25 | }; 26 | return { 27 | endpoints: [healthEndpoint], 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/infrastructure/models/index.ts: -------------------------------------------------------------------------------- 1 | import { UserModel } from './User.model'; 2 | import { USER_MODEL_PROVIDER, DB_PROVIDER } from '@constants'; 3 | 4 | export const modelProviders = [{ 5 | provide: USER_MODEL_PROVIDER, 6 | useValue: UserModel, 7 | inject: [DB_PROVIDER], 8 | }]; 9 | -------------------------------------------------------------------------------- /src/infrastructure/models/user.model.ts: -------------------------------------------------------------------------------- 1 | import * as faker from 'faker'; 2 | 3 | export class UserModel { 4 | constructor(user: UserModel | any) { 5 | this._id = faker.random.uuid(); 6 | this.name = user.name; 7 | this.lastname = user.lastname; 8 | this.age = user.age; 9 | } 10 | 11 | _id?: string; 12 | name: string; 13 | lastname: string; 14 | age: number; 15 | 16 | save(): UserModel { 17 | return this; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/infrastructure/repository/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@nestjs/common'; 2 | import { User } from '@domain/entities/User'; 3 | import { UserModel } from '@infrastructure/models/User.model'; 4 | import { CreateUserDto } from '@application/dto/create-user.dto'; 5 | import { USER_MODEL_PROVIDER } from '@constants'; 6 | 7 | // Se inyecta el repo en el servicio 8 | @Injectable() 9 | export class UserRepository { 10 | constructor(@Inject(USER_MODEL_PROVIDER) private readonly model: User) {} 11 | 12 | async create(user: CreateUserDto): Promise { 13 | const newUser = new UserModel(user); 14 | return await newUser; 15 | } 16 | 17 | async find(): Promise { 18 | const allUser: User[] = [ 19 | new UserModel({ name: 'Mertie', lastname: 'Beier', age: 10 }), 20 | new UserModel({name: 'Ana', lastname: 'Ziemann', age : 10}), 21 | new UserModel({name: 'Martha', lastname: 'Becerra', age : 10}), 22 | ]; 23 | 24 | return await allUser; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | // Only For module alias 2 | import 'module-alias/register'; 3 | import * as path from 'path'; 4 | import * as moduleAlias from 'module-alias'; 5 | moduleAlias.addAliases({ 6 | '@domain': path.resolve(__dirname, 'domain'), 7 | '@application': path.resolve(__dirname, 'application'), 8 | '@infrastructure': path.resolve(__dirname, 'infrastructure'), 9 | '@constants': path.format({dir: __dirname, name: 'constants'}), 10 | }); 11 | 12 | // App modules 13 | import { NestFactory } from '@nestjs/core'; 14 | import { AppModule } from './app.module'; 15 | import { APP_PORT } from './constants'; 16 | import { ValidationPipe } from '@nestjs/common'; 17 | 18 | async function bootstrap() { 19 | const app = await NestFactory.create(AppModule); 20 | app.useGlobalPipes(new ValidationPipe()); 21 | await app.listen(APP_PORT); 22 | 23 | console.log('Runing on port ==> ', APP_PORT); 24 | } 25 | bootstrap(); 26 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppModule } from '../src/app.module'; 3 | import * as request from 'supertest'; 4 | import * as faker from 'faker'; 5 | jest.setTimeout(30000); 6 | 7 | describe('AppController (e2e)', () => { 8 | let app: any; 9 | const expected = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 10 | 11 | function generateData() { 12 | const user = { 13 | name: faker.name.findName(), 14 | lastname: faker.name.lastName(), 15 | age: faker.random.number(), 16 | }; 17 | return user; 18 | } 19 | 20 | beforeAll(async () => { 21 | const moduleFixture: TestingModule = await Test.createTestingModule({ 22 | imports: [AppModule], 23 | }).compile(); 24 | 25 | app = moduleFixture.createNestApplication(); 26 | await app.init(); 27 | }); 28 | 29 | afterAll(() => { console.log('Done'); }); 30 | 31 | it('/ (GET)', () => { 32 | return request(app.getHttpServer()) 33 | .get('/hello') 34 | .expect(200) 35 | .expect('Hello World!'); 36 | }); 37 | 38 | it('/ (POST)', async () => { 39 | const user = generateData(); 40 | return request(app.getHttpServer()) 41 | .post('/') 42 | .send(user) 43 | .set('Accept', 'application/json') 44 | .expect('Content-Type', /json/) 45 | .expect(201) 46 | .expect((r) => { 47 | expect(r.body._id).toBeDefined(); 48 | expect(r.body.name).toEqual(user.name); 49 | expect(r.body.lastname).toEqual(user.lastname); 50 | expect(r.body.age).toEqual(user.age); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es6", 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "baseUrl": "./", 12 | "incremental": true, 13 | "paths": { 14 | "@constants": ["src/constants"], 15 | "@domain/*": ["src/domain/*"], 16 | "@application/*": ["src/application/*"], 17 | "@infrastructure/*": ["src/infrastructure/*"] 18 | } 19 | }, 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false, 16 | "no-console": false 17 | }, 18 | "paths": { 19 | "@domain/*": ["src/domain/*"], 20 | "@infrastructure/*": ["src/infrastructure/*"], 21 | "@application/*": ["src/application/*"] 22 | }, 23 | "rulesDirectory": [] 24 | } 25 | --------------------------------------------------------------------------------