├── README.md ├── typescript-ddd-cqrs-example ├── test.txt ├── src │ ├── Mooc │ │ └── Videos │ │ │ ├── Domain │ │ │ ├── VideoId.ts │ │ │ ├── VideoTitle.ts │ │ │ ├── VideoDuration.ts │ │ │ ├── ModifyVideoDurationDTO.ts │ │ │ ├── CreateVideoDTO.ts │ │ │ ├── IVideoRepository.ts │ │ │ └── Video.ts │ │ │ ├── Application │ │ │ ├── GetVideoQuery.ts │ │ │ ├── SearchVideo.ts │ │ │ ├── TrimVideo.ts │ │ │ ├── TrimVideoHandler.ts │ │ │ ├── CreateVideoHandler.ts │ │ │ ├── GetVideoHandler.ts │ │ │ ├── SearchVideoHandler.ts │ │ │ ├── CreateVideoCommand.ts │ │ │ ├── TrimVideoCommand.ts │ │ │ └── CreateVideo.ts │ │ │ ├── Infraestructure │ │ │ ├── MemoryVideoRepository.ts │ │ │ └── VideoController.ts │ │ │ └── VideosModule.ts │ ├── Shared │ │ ├── Domain │ │ │ └── Bus │ │ │ │ ├── Query │ │ │ │ ├── IQuery.ts │ │ │ │ ├── IResponse.ts │ │ │ │ ├── IQueryHandler.ts │ │ │ │ └── IQueryBus.ts │ │ │ │ └── Command │ │ │ │ ├── ICommand.ts │ │ │ │ ├── IAsyncCommand.ts │ │ │ │ ├── ICommandHandler.ts │ │ │ │ └── ICommandBus.ts │ │ └── Infraestructure │ │ │ ├── Bus │ │ │ ├── FileCommandBusAsync.ts │ │ │ ├── CommandBusInMemorySync.ts │ │ │ └── QueryBusInMemorySync.ts │ │ │ └── RequestCommandAsyncPooler.ts │ ├── app.module.ts │ └── main.ts ├── .prettierrc ├── nest-cli.json ├── tsconfig.build.json ├── test │ ├── jest-e2e.json │ ├── Infraestructure │ │ └── Bus │ │ │ └── FileCommandBusAsync.test.ts │ ├── app.e2e-spec.ts │ └── Videos │ │ ├── Application │ │ └── CreateVideo.test.ts │ │ └── Infraestructure │ │ ├── MemoryVideoRepository.test.ts │ │ └── VideoController.test.ts ├── tsconfig.json ├── .gitignore ├── .eslintrc.js ├── package.json └── README.md ├── docker ├── simple-prog │ ├── index.php │ ├── Dockerfile │ └── README.md ├── cmd-vs-entrypoint │ ├── Dockerfile │ └── README.md └── env-vars │ ├── index.php │ ├── php.ini │ ├── Dockerfile │ └── README.md ├── typescript-ddd-example ├── src │ ├── Mooc │ │ └── Videos │ │ │ ├── Domain │ │ │ ├── VideoId.ts │ │ │ ├── VideoTitle.ts │ │ │ ├── VideoDuration.ts │ │ │ ├── CreateVideoDTO.ts │ │ │ ├── IVideoRepository.ts │ │ │ └── Video.ts │ │ │ ├── Application │ │ │ ├── SearchVideo.ts │ │ │ └── CreateVideo.ts │ │ │ ├── VideosModule.ts │ │ │ └── Infraestructure │ │ │ ├── VideoController.ts │ │ │ └── MemoryVideoRepository.ts │ ├── app.module.ts │ └── main.ts ├── .prettierrc ├── nest-cli.json ├── tsconfig.build.json ├── test │ ├── jest-e2e.json │ ├── app.e2e-spec.ts │ └── Videos │ │ ├── Application │ │ └── CreateVideo.test.ts │ │ └── Infraestructure │ │ ├── MemoryVideoRepository.test.ts │ │ └── VideoController.test.ts ├── tsconfig.json ├── .gitignore ├── .eslintrc.js ├── package.json └── README.md └── GildedRose ├── test ├── mocha.opts ├── gilded-rose.spec.ts └── golden-master-text-test.ts ├── .gitignore ├── tsconfig.json ├── package.json ├── README.md └── app └── gilded-rose.ts /README.md: -------------------------------------------------------------------------------- 1 | # Katas 2 | Just a collection of Katas 3 | -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/test.txt: -------------------------------------------------------------------------------- 1 | [object Promise] 2 | -------------------------------------------------------------------------------- /docker/simple-prog/index.php: -------------------------------------------------------------------------------- 1 | { 4 | let fileCommandBus: FileCommandBusAsync; 5 | let mockAsyncCommand = {serialize: jest.fn()} 6 | 7 | beforeEach(async () => { 8 | fileCommandBus = new FileCommandBusAsync('test.txt') 9 | mockAsyncCommand.serialize.mockResolvedValue('a;b;60') 10 | }); 11 | 12 | it('Should append correctly one line in a new file', () => { 13 | //Arrange 14 | //Act 15 | fileCommandBus.dispatch(mockAsyncCommand) 16 | //Assert 17 | 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /typescript-ddd-example/src/Mooc/Videos/VideosModule.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { VideoController } from './Infraestructure/VideoController'; 3 | import { CreateVideo } from './Application/createVideo'; 4 | import { SearchVideo } from './Application/SearchVideo'; 5 | import { MemoryVideoRepository } from './Infraestructure/MemoryVideoRepository'; 6 | 7 | @Module({ 8 | controllers: [VideoController], 9 | providers: [CreateVideo, SearchVideo, 10 | { //Here is where I do the dependency injection, as Nestjs resolves de dependencies by name and can't infere by type 11 | provide: 'IVideoRepository', 12 | useClass: MemoryVideoRepository 13 | }], 14 | }) 15 | export class VideosModule {} -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/src/Shared/Infraestructure/Bus/FileCommandBusAsync.ts: -------------------------------------------------------------------------------- 1 | import { ICommandBus } from "../../Domain/Bus/Command/ICommandBus"; 2 | import { IAsyncCommand } from "src/Shared/Domain/Bus/Command/IAsyncCommand"; 3 | import fs = require('fs') 4 | 5 | export class FileCommandBusAsync implements ICommandBus { 6 | constructor(private readonly filename: string){} 7 | 8 | dispatch(command: IAsyncCommand): void { 9 | fs.createWriteStream(this.filename, {flags: 'a'}) //append 10 | fs.appendFileSync(this.filename, command.serialize()+"\n"); 11 | } 12 | 13 | handler(commandClassName: string, commandHandler: any): void { 14 | //This is empty cause the async command bus is not redirecting anything 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /typescript-ddd-example/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from '../src/app/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from '../src/app/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/src/Shared/Infraestructure/Bus/CommandBusInMemorySync.ts: -------------------------------------------------------------------------------- 1 | import { ICommandBus } from "../../Domain/Bus/Command/ICommandBus"; 2 | import { ICommand } from "../../Domain/Bus/Command/ICommand"; 3 | import { ICommandHandler } from '../../../Shared/Domain/Bus/Command/ICommandHandler' 4 | 5 | export class CommandBusInMemorySync implements ICommandBus { 6 | constructor(private readonly route: Map){} 7 | 8 | dispatch(command: ICommand): void { 9 | const commandName: string = command.constructor.name 10 | this.route.get(commandName).invoke(command); 11 | } 12 | 13 | handler(commandClassName: string, commandHandler: any): void { 14 | this.route.set(commandClassName, commandHandler); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module' 3 | import { RequestCommandAsyncPooler } from './Shared/Infraestructure/RequestCommandAsyncPooler' 4 | import { memoryVideoRepo } from './Mooc/Videos/VideosModule' 5 | import { TrimVideoHandler } from './Mooc/Videos/Application/TrimVideoHandler'; 6 | import { TrimVideo } from './Mooc/Videos/Application/TrimVideo'; 7 | 8 | async function bootstrap() { 9 | const app = await NestFactory.create(AppModule); 10 | const poolAsyncReq = new RequestCommandAsyncPooler('serialized_commands.txt', new TrimVideoHandler(new TrimVideo(memoryVideoRepo)) ); 11 | (async () => { 12 | poolAsyncReq.poolingProcess(); 13 | })(); 14 | await app.listen(3000); 15 | } 16 | bootstrap(); 17 | -------------------------------------------------------------------------------- /typescript-ddd-example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'prettier/@typescript-eslint', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'prettier/@typescript-eslint', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/src/Shared/Infraestructure/Bus/QueryBusInMemorySync.ts: -------------------------------------------------------------------------------- 1 | import { IQueryBus } from "../../Domain/Bus/Query/IQueryBus"; 2 | import { IQuery } from "../../Domain/Bus/Query/IQuery"; 3 | import { IQueryHandler } from '../../../Shared/Domain/Bus/Query/IQueryHandler' 4 | import { IResponse } from "../../Domain/Bus/Query/IResponse"; 5 | 6 | export class QueryBusInMemorySync implements IQueryBus { 7 | constructor(private readonly route: Map){} 8 | 9 | dispatch(command: IQuery): IResponse { 10 | const commandName: string = command.constructor.name 11 | return this.route.get(commandName).ask(command); 12 | } 13 | 14 | handler(commandClassName: string, commandHandler: any): void { 15 | this.route.set(commandClassName, commandHandler); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /typescript-ddd-example/src/Mooc/Videos/Infraestructure/VideoController.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, Post, Body } from '@nestjs/common'; 2 | import {CreateVideo} from '../Application/CreateVideo' 3 | import {SearchVideo} from '../Application/SearchVideo' 4 | import { Video } from '../Domain/Video'; 5 | import { CreateVideoDTO } from '../Domain/CreateVideoDTO'; 6 | 7 | @Controller() 8 | export class VideoController { 9 | constructor(private readonly createServ: CreateVideo, private readonly searchServ: SearchVideo) {} 10 | 11 | @Get(':id') 12 | getVideo(@Param('id') id: string): Video { 13 | return this.searchServ.search(id); 14 | } 15 | 16 | @Post() 17 | create(@Body() createVideoDto: CreateVideoDTO): void { 18 | this.createServ.create(createVideoDto.id, createVideoDto.title, createVideoDto.duration); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /typescript-ddd-example/src/Mooc/Videos/Infraestructure/MemoryVideoRepository.ts: -------------------------------------------------------------------------------- 1 | import { IVideoRepository } from '../Domain/IVideoRepository'; 2 | import { Video } from '../Domain/Video'; 3 | import { VideoId } from '../Domain/VideoId'; 4 | import { Injectable } from '@nestjs/common'; 5 | 6 | @Injectable() 7 | export class MemoryVideoRepository implements IVideoRepository{ 8 | private memory: Video[]; 9 | 10 | constructor(){ 11 | this.memory = []; 12 | } 13 | 14 | createVideo(video: Video) { 15 | this.memory.push(video) 16 | } 17 | 18 | searchVideo(videoId: VideoId): Video { 19 | const videoFound = this.memory.find(v => v.id === videoId) 20 | if(!videoFound){ 21 | throw new Error(`Video with ID: ${videoId} not found`) 22 | } 23 | return videoFound; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/src/Mooc/Videos/Infraestructure/MemoryVideoRepository.ts: -------------------------------------------------------------------------------- 1 | import { IVideoRepository } from '../Domain/IVideoRepository'; 2 | import { Video } from '../Domain/Video'; 3 | import { VideoId } from '../Domain/VideoId'; 4 | import { Injectable } from '@nestjs/common'; 5 | 6 | @Injectable() 7 | export class MemoryVideoRepository implements IVideoRepository{ 8 | private memory: Video[]; 9 | 10 | constructor(){ 11 | this.memory = []; 12 | } 13 | 14 | createVideo(video: Video) { 15 | this.memory.push(video) 16 | } 17 | 18 | searchVideo(videoId: VideoId): Video { 19 | const videoFound = this.memory.find(v => v.id === videoId) 20 | if(!videoFound){ 21 | throw new Error(`Video with ID: ${videoId} not found`) 22 | } 23 | return videoFound; 24 | } 25 | 26 | trimVideo(id: string, newDuration: number) { 27 | const video = this.searchVideo(id); 28 | video.duration = newDuration; 29 | } 30 | } -------------------------------------------------------------------------------- /GildedRose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gilded-rose-kata", 3 | "version": "1.0.0", 4 | "description": "Gilded Rose kata in TypeScript", 5 | "scripts": { 6 | "precompile": "rimraf app/**/*.js test/**/*.js", 7 | "compile": "tsc", 8 | "pretest": "rimraf app/**/*.js test/**/*.js", 9 | "test": "nyc mocha" 10 | }, 11 | "license": "MIT", 12 | "private": true, 13 | "devDependencies": { 14 | "@types/chai": "~3.5.2", 15 | "@types/mocha": "~2.2.41", 16 | "@types/node": "~7.0.18", 17 | "chai": "~3.5.0", 18 | "mocha": "~3.2.0", 19 | "nyc": "~11.0.3", 20 | "rimraf": "~2.5.2", 21 | "source-map-support": "0.5.9", 22 | "ts-node": "~3.1.0", 23 | "typescript": "~3.0.3" 24 | }, 25 | "nyc": { 26 | "extension": [ 27 | ".ts" 28 | ], 29 | "exclude": [ 30 | "**/*.d.ts", 31 | "test/**" 32 | ], 33 | "require": [ 34 | "ts-node/register" 35 | ], 36 | "reporter": [ 37 | "html", 38 | "text" 39 | ] 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /typescript-ddd-example/test/Videos/Application/CreateVideo.test.ts: -------------------------------------------------------------------------------- 1 | import { CreateVideo } from '../../../src/Mooc/Videos/Application/CreateVideo'; 2 | import { VideoId } from '../../../src/Mooc/Videos/Domain/VideoId'; 3 | import { VideoTitle } from '../../../src/Mooc/Videos/Domain/VideoTitle'; 4 | import { VideoDuration } from '../../../src/Mooc/Videos/Domain/VideoDuration'; 5 | 6 | describe('Test for the createVideo application service', () => { 7 | let videoCreateService: CreateVideo; 8 | const videoRepository = { createVideo: jest.fn(), searchVideo: jest.fn() }; 9 | 10 | beforeEach(async () => { 11 | videoCreateService = new CreateVideo(videoRepository) 12 | }); 13 | 14 | it('Should create a video when the arguments passed are correct', () => { 15 | //Arrange 16 | const videoId: VideoId = '1'; 17 | const videoTitle: VideoTitle = 'Title1'; 18 | const videoDuration: VideoDuration = 5; 19 | //Act 20 | videoCreateService.create(videoId, videoTitle, videoDuration) 21 | //Assert 22 | expect(videoRepository.createVideo).toHaveBeenCalledTimes(1); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /GildedRose/test/golden-master-text-test.ts: -------------------------------------------------------------------------------- 1 | import { Item, GildedRose } from '../app/gilded-rose'; 2 | 3 | const items = [ 4 | new Item("+5 Dexterity Vest", 10, 20), // 5 | new Item("Aged Brie", 2, 0), // 6 | new Item("Elixir of the Mongoose", 5, 7), // 7 | new Item("Sulfuras, Hand of Ragnaros", 0, 80), // 8 | new Item("Sulfuras, Hand of Ragnaros", -1, 80), 9 | new Item("Backstage passes to a TAFKAL80ETC concert", 15, 20), 10 | new Item("Backstage passes to a TAFKAL80ETC concert", 10, 49), 11 | new Item("Backstage passes to a TAFKAL80ETC concert", 5, 49), 12 | // this conjured item does not work properly yet 13 | new Item("Conjured Mana Cake", 3, 6)]; 14 | 15 | 16 | const gildedRose = new GildedRose(items); 17 | var days: number = 2; 18 | for (let i = 0; i < days; i++) { 19 | console.log("-------- day " + i + " --------"); 20 | console.log("name, sellIn, quality"); 21 | items.forEach(element => { 22 | console.log(element.name + ' ' + element.sellIn + ' ' + element.quality); 23 | 24 | }); 25 | console.log(); 26 | gildedRose.updateQuality(); 27 | } -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/test/Videos/Application/CreateVideo.test.ts: -------------------------------------------------------------------------------- 1 | import { CreateVideo } from '../../../src/Mooc/Videos/Application/CreateVideo'; 2 | import { VideoId } from '../../../src/Mooc/Videos/Domain/VideoId'; 3 | import { VideoTitle } from '../../../src/Mooc/Videos/Domain/VideoTitle'; 4 | import { VideoDuration } from '../../../src/Mooc/Videos/Domain/VideoDuration'; 5 | 6 | describe('Test for the createVideo application service', () => { 7 | let videoCreateService: CreateVideo; 8 | const videoRepository = { createVideo: jest.fn(), searchVideo: jest.fn() }; 9 | 10 | beforeEach(async () => { 11 | videoCreateService = new CreateVideo(videoRepository) 12 | }); 13 | 14 | it('Should create a video when the arguments passed are correct', () => { 15 | //Arrange 16 | const videoId: VideoId = '1'; 17 | const videoTitle: VideoTitle = 'Title1'; 18 | const videoDuration: VideoDuration = 5; 19 | //Act 20 | videoCreateService.create(videoId, videoTitle, videoDuration) 21 | //Assert 22 | expect(videoRepository.createVideo).toHaveBeenCalledTimes(1); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /typescript-ddd-example/test/Videos/Infraestructure/MemoryVideoRepository.test.ts: -------------------------------------------------------------------------------- 1 | import { MemoryVideoRepository } from '../../../src/Mooc/Videos/Infraestructure/MemoryVideoRepository'; 2 | import { VideoId } from '../../../src/Mooc/Videos/Domain/VideoId'; 3 | import { VideoTitle } from '../../../src/Mooc/Videos/Domain/VideoTitle'; 4 | import { VideoDuration } from '../../../src/Mooc/Videos/Domain/VideoDuration'; 5 | import { Video } from '../../../src/Mooc/Videos/Domain/Video'; 6 | 7 | describe('Test for the MemoryVideoRepository adapter', () => { 8 | let memVideoRepo: MemoryVideoRepository; 9 | 10 | beforeEach(async () => { 11 | memVideoRepo = new MemoryVideoRepository() 12 | }); 13 | 14 | it('Should store a video in the repo and being able to retrieve it', () => { 15 | //Arrange 16 | const videoId: VideoId = '1'; 17 | const videoTitle: VideoTitle = 'Title1' 18 | const videoDuration: VideoDuration = 5; 19 | const video = new Video(videoId, videoTitle, videoDuration); 20 | //Act 21 | memVideoRepo.createVideo(video); 22 | //Assert 23 | expect(memVideoRepo.searchVideo(videoId)).toEqual(video); 24 | }); 25 | 26 | it('Should throw an Error when trying to access a Video does not exist on the repo', () => { 27 | //Act + Assert 28 | expect(memVideoRepo.searchVideo).toThrowError(); 29 | }) 30 | }); 31 | -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/test/Videos/Infraestructure/MemoryVideoRepository.test.ts: -------------------------------------------------------------------------------- 1 | import { MemoryVideoRepository } from '../../../src/Mooc/Videos/Infraestructure/MemoryVideoRepository'; 2 | import { VideoId } from '../../../src/Mooc/Videos/Domain/VideoId'; 3 | import { VideoTitle } from '../../../src/Mooc/Videos/Domain/VideoTitle'; 4 | import { VideoDuration } from '../../../src/Mooc/Videos/Domain/VideoDuration'; 5 | import { Video } from '../../../src/Mooc/Videos/Domain/Video'; 6 | 7 | describe('Test for the MemoryVideoRepository adapter', () => { 8 | let memVideoRepo: MemoryVideoRepository; 9 | 10 | beforeEach(async () => { 11 | memVideoRepo = new MemoryVideoRepository() 12 | }); 13 | 14 | it('Should store a video in the repo and being able to retrieve it', () => { 15 | //Arrange 16 | const videoId: VideoId = '1'; 17 | const videoTitle: VideoTitle = 'Title1' 18 | const videoDuration: VideoDuration = 5; 19 | const video = new Video(videoId, videoTitle, videoDuration); 20 | //Act 21 | memVideoRepo.createVideo(video); 22 | //Assert 23 | expect(memVideoRepo.searchVideo(videoId)).toEqual(video); 24 | }); 25 | 26 | it('Should throw an Error when trying to access a Video does not exist on the repo', () => { 27 | //Act + Assert 28 | expect(memVideoRepo.searchVideo).toThrowError(); 29 | }) 30 | }); 31 | -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/src/Mooc/Videos/Infraestructure/VideoController.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, Post, Body, Inject, Patch } from '@nestjs/common'; 2 | import { CreateVideoDTO } from '../Domain/CreateVideoDTO'; 3 | import { ModifyVideoDurationDTO } from '../Domain/ModifyVideoDurationDTO'; 4 | import { CreateVideoCommand } from '../Application/CreateVideoCommand'; 5 | import { ICommandBus } from '../../../../src/Shared/Domain/Bus/Command/ICommandBus'; 6 | import { IQueryBus } from '../../../../src/Shared/Domain/Bus/Query/IQueryBus'; 7 | import { GetVideoQuery } from '../Application/GetVideoQuery' 8 | import { IResponse } from '../../../../src/Shared/Domain/Bus/Query/IResponse'; 9 | import { TrimVideoCommand } from '../Application/TrimVideoCommand'; 10 | 11 | @Controller() 12 | export class VideoController { 13 | constructor( 14 | @Inject('ICommandBus') private readonly commandBus: ICommandBus, 15 | @Inject('IQueryBus') private readonly queryBus: IQueryBus, 16 | @Inject('ICommandAsyncBus') private readonly asyncCommandBus: ICommandBus 17 | ) {} 18 | 19 | @Get(':id') 20 | getVideo(@Param('id') id: string): IResponse { 21 | const getVideoQuery: GetVideoQuery = new GetVideoQuery(id); 22 | return this.queryBus.dispatch(getVideoQuery); 23 | } 24 | 25 | @Post() 26 | create(@Body() createVideoDto: CreateVideoDTO): void { 27 | const createVideoCommand: CreateVideoCommand = new CreateVideoCommand(createVideoDto.id, createVideoDto.title, createVideoDto.duration) 28 | this.commandBus.dispatch(createVideoCommand); 29 | } 30 | 31 | @Patch() 32 | modifyVideo(@Body() modifyVideoDto: ModifyVideoDurationDTO): void { 33 | const trimVideoCmd: TrimVideoCommand = new TrimVideoCommand(modifyVideoDto.id, modifyVideoDto.duration); 34 | return this.asyncCommandBus.dispatch(trimVideoCmd); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/src/Mooc/Videos/VideosModule.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { VideoController } from './Infraestructure/VideoController'; 3 | import { MemoryVideoRepository } from './Infraestructure/MemoryVideoRepository'; 4 | import { CommandBusInMemorySync } from 'src/Shared/Infraestructure/Bus/CommandBusInMemorySync'; 5 | import { QueryBusInMemorySync } from 'src/Shared/Infraestructure/Bus/QueryBusInMemorySync'; 6 | import { CreateVideoCommand } from './Application/CreateVideoCommand'; 7 | import { CreateVideoHandler } from './Application/CreateVideoHandler'; 8 | import { CreateVideo } from './Application/CreateVideo'; 9 | import { GetVideoQuery } from './Application/GetVideoQuery'; 10 | import { GetVideoHandler } from './Application/GetVideoHandler'; 11 | import { SearchVideo } from './Application/SearchVideo'; 12 | import { FileCommandBusAsync } from '../../Shared/Infraestructure/Bus/FileCommandBusAsync'; 13 | 14 | export const memoryVideoRepo = new MemoryVideoRepository(); 15 | 16 | @Module({ 17 | controllers: [VideoController], 18 | providers: [ 19 | { //Here is where I do the dependency injection, as Nestjs resolves de dependencies by name and can't infere by type 20 | provide: 'IVideoRepository', 21 | useClass: MemoryVideoRepository 22 | }, 23 | { 24 | provide: 'ICommandBus', 25 | useValue: new CommandBusInMemorySync(new Map() 26 | .set(CreateVideoCommand.name, new CreateVideoHandler(new CreateVideo(memoryVideoRepo)))) 27 | }, 28 | { 29 | provide: 'IQueryBus', 30 | useValue: new QueryBusInMemorySync(new Map() 31 | .set(GetVideoQuery.name, new GetVideoHandler(new SearchVideo(memoryVideoRepo)))) 32 | }, 33 | { 34 | provide: 'ICommandAsyncBus', 35 | useValue: new FileCommandBusAsync('serialized_commands.txt') 36 | } 37 | ], 38 | }) 39 | export class VideosModule {} -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/src/Shared/Infraestructure/RequestCommandAsyncPooler.ts: -------------------------------------------------------------------------------- 1 | const readline = require('readline'); 2 | import fs = require('fs') 3 | import { TrimVideoCommand } from '../../Mooc/Videos/Application/TrimVideoCommand' 4 | import { ICommandHandler } from '../../Shared/Domain/Bus/Command/ICommandHandler' 5 | 6 | export class RequestCommandAsyncPooler { 7 | constructor(private readonly requestFileName: string, private readonly modifyDurationHandler: ICommandHandler){} 8 | 9 | private delay(ms: number) { 10 | return new Promise( resolve => setTimeout(resolve, ms) ); 11 | } 12 | 13 | private unserializeCommand(line: string): TrimVideoCommand { 14 | const chunks = line.split(';') 15 | const newDuration: number = +chunks[2] 16 | return new TrimVideoCommand(chunks[1], newDuration) 17 | } 18 | 19 | public async poolingProcess(){ 20 | console.log('Started async commnads pooling process...') 21 | const self = this; 22 | while(1){ 23 | fs.exists(this.requestFileName, (exists) => { 24 | if(exists){ 25 | var lineReader = readline.createInterface({ 26 | input: fs.createReadStream(this.requestFileName) 27 | }); 28 | lineReader.on('line', function (line: string) { 29 | console.log('Command from file:', line); 30 | if(line.length > 3){ 31 | const cmdAsync: TrimVideoCommand = self.unserializeCommand(line); 32 | self.modifyDurationHandler.invoke(cmdAsync) 33 | } 34 | }); 35 | fs.unlink(this.requestFileName, (err) => { 36 | }) 37 | } 38 | }) 39 | await this.delay(1000); //Just create a delay to simulate the asynchronicity 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /GildedRose/README.md: -------------------------------------------------------------------------------- 1 | **Solution is in fix/refactor branch** 2 | 3 | # Gilded Rose Refactoring Kata 4 | 5 | Hi and welcome to team Gilded Rose. As you know, we are a small inn with a prime location in a prominent city ran by a friendly innkeeper named Allison. We also buy and sell only the finest goods. Unfortunately, our goods are constantly degrading in quality as they approach their sell by date. We have a system in place that updates our inventory for us. It was developed by a no-nonsense type named Leeroy, who has moved on to new adventures. Your task is to add the new feature to our system so that we can begin selling a new category of items. First an introduction to our system: 6 | 7 | All items have a SellIn value which denotes the number of days we have to sell the item 8 | All items have a Quality value which denotes how valuable the item is 9 | At the end of each day our system lowers both values for every item 10 | Pretty simple, right? Well this is where it gets interesting: 11 | 12 | Once the sell by date has passed, Quality degrades twice as fast 13 | The Quality of an item is never negative 14 | "Aged Brie" actually increases in Quality the older it gets 15 | The Quality of an item is never more than 50 16 | "Sulfuras", being a legendary item, never has to be sold or decreases in Quality 17 | "Backstage passes", like aged brie, increases in Quality as it's SellIn value approaches; Quality increases by 2 when there are 10 days or less and by 3 when there are 5 days or less but Quality drops to 0 after the concert 18 | We have recently signed a supplier of conjured items. This requires an update to our system: 19 | 20 | "Conjured" items degrade in Quality twice as fast as normal items 21 | Feel free to make any changes to the UpdateQuality method and add any new code as long as everything still works correctly. However, do not alter the Item class or Items property as those belong to the goblin in the corner who will insta-rage and one-shot you as he doesn't believe in shared code ownership (you can make the UpdateQuality method and Items property static if you like, we'll cover for you). 22 | 23 | Just for clarification, an item can never have its Quality increase above 50, however "Sulfuras" is a legendary item and as such its Quality is 80 and it never alters. 24 | 25 | -------------------------------------------------------------------------------- /typescript-ddd-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-ddd-example", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "ivanob", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/app", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@nestjs/common": "^7.5.1", 25 | "@nestjs/core": "^7.5.1", 26 | "@nestjs/platform-express": "^7.5.1", 27 | "reflect-metadata": "^0.1.13", 28 | "rimraf": "^3.0.2", 29 | "rxjs": "^6.6.3" 30 | }, 31 | "devDependencies": { 32 | "@nestjs/cli": "^7.5.1", 33 | "@nestjs/schematics": "^7.1.3", 34 | "@nestjs/testing": "^7.6.4", 35 | "@types/express": "^4.17.8", 36 | "@types/jest": "^26.0.15", 37 | "@types/node": "^14.14.6", 38 | "@types/supertest": "^2.0.10", 39 | "@typescript-eslint/eslint-plugin": "^4.6.1", 40 | "@typescript-eslint/parser": "^4.6.1", 41 | "eslint": "^7.12.1", 42 | "eslint-config-prettier": "7.1.0", 43 | "eslint-plugin-prettier": "^3.1.4", 44 | "jest": "^26.6.3", 45 | "prettier": "^2.1.2", 46 | "supertest": "^6.0.0", 47 | "ts-jest": "^26.4.3", 48 | "ts-loader": "^8.0.8", 49 | "ts-node": "^9.0.0", 50 | "tsconfig-paths": "^3.9.0", 51 | "typescript": "^4.0.5" 52 | }, 53 | "jest": { 54 | "moduleFileExtensions": [ 55 | "js", 56 | "json", 57 | "ts" 58 | ], 59 | "rootDir": "./test", 60 | "testRegex": ".*\\.test\\.ts$", 61 | "transform": { 62 | "^.+\\.(t|j)s$": "ts-jest" 63 | }, 64 | "collectCoverageFrom": [ 65 | "**/*.(t|j)s" 66 | ], 67 | "coverageDirectory": "../coverage", 68 | "testEnvironment": "node" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-ddd-cqrs-example", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "ivanob", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/app", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@nestjs/common": "^7.5.1", 25 | "@nestjs/core": "^7.5.1", 26 | "@nestjs/platform-express": "^7.5.1", 27 | "reflect-metadata": "^0.1.13", 28 | "rimraf": "^3.0.2", 29 | "rxjs": "^6.6.3" 30 | }, 31 | "devDependencies": { 32 | "@nestjs/cli": "^7.5.1", 33 | "@nestjs/schematics": "^7.1.3", 34 | "@nestjs/testing": "^7.6.4", 35 | "@types/express": "^4.17.8", 36 | "@types/jest": "^26.0.15", 37 | "@types/node": "^14.14.6", 38 | "@types/supertest": "^2.0.10", 39 | "@typescript-eslint/eslint-plugin": "^4.6.1", 40 | "@typescript-eslint/parser": "^4.6.1", 41 | "eslint": "^7.12.1", 42 | "eslint-config-prettier": "7.1.0", 43 | "eslint-plugin-prettier": "^3.1.4", 44 | "jest": "^26.6.3", 45 | "prettier": "^2.1.2", 46 | "supertest": "^6.0.0", 47 | "ts-jest": "^26.4.3", 48 | "ts-loader": "^8.0.8", 49 | "ts-node": "^9.0.0", 50 | "tsconfig-paths": "^3.9.0", 51 | "typescript": "^4.0.5" 52 | }, 53 | "jest": { 54 | "moduleFileExtensions": [ 55 | "js", 56 | "json", 57 | "ts" 58 | ], 59 | "rootDir": "./test", 60 | "testRegex": ".*\\.test\\.ts$", 61 | "transform": { 62 | "^.+\\.(t|j)s$": "ts-jest" 63 | }, 64 | "collectCoverageFrom": [ 65 | "**/*.(t|j)s" 66 | ], 67 | "coverageDirectory": "../coverage", 68 | "testEnvironment": "node" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /typescript-ddd-example/test/Videos/Infraestructure/VideoController.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { VideoController } from '../../../src/Mooc/Videos/Infraestructure/VideoController'; 3 | import { SearchVideo } from '../../../src/Mooc/Videos/Application/SearchVideo'; 4 | import { CreateVideo } from '../../../src/Mooc/Videos/Application/createVideo'; 5 | import { Video } from '../../../src/Mooc/Videos/Domain/Video'; 6 | import { VideoId } from '../../../src/Mooc/Videos/Domain/VideoId'; 7 | import { CreateVideoDTO } from '../../../src/Mooc/Videos/Domain/CreateVideoDTO'; 8 | 9 | describe('VideoController', () => { 10 | let videoController: VideoController; 11 | const mockVideo = new Video('2', 'abc', 60); 12 | /* This mock represents the MemoryVideoRepository, that is used by the controller 13 | in its injected application services: CreateVideo and SearchVideo */ 14 | const memoryRepoMock: jest.Mock = jest.fn((): {} => ({ 15 | createVideo: (video: Video): void => { 16 | return; 17 | }, 18 | searchVideo: (videoId: VideoId): Video => { 19 | if(videoId === '2'){ 20 | return mockVideo 21 | } 22 | return undefined; 23 | } 24 | })); 25 | 26 | beforeEach(async () => { 27 | const app: TestingModule = await Test.createTestingModule({ 28 | controllers: [VideoController], 29 | providers: [SearchVideo, CreateVideo, { 30 | provide: 'IVideoRepository', //I inject the dependency manually here 31 | useClass: memoryRepoMock 32 | }], 33 | }).compile(); 34 | 35 | videoController = app.get(VideoController); 36 | }); 37 | 38 | describe('getVideo()', () => { 39 | it('should return undefined if we try to GET a video that does not exist', () => { 40 | expect(videoController.getVideo('1')).toBe(undefined); 41 | }); 42 | 43 | it('should return a valid Video if we try to GET a video that exists', () => { 44 | expect(videoController.getVideo('2')).toEqual(mockVideo); 45 | }); 46 | }); 47 | 48 | describe('create()', () => { 49 | it('should create a valid video that is passed via POST', () => { 50 | //Arrange 51 | const dtoCreate = new CreateVideoDTO('1', 'abc', 60) 52 | //Act + Assert 53 | expect(videoController.create(dtoCreate)).toBe(undefined); 54 | }); 55 | 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/test/Videos/Infraestructure/VideoController.test.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { VideoController } from '../../../src/Mooc/Videos/Infraestructure/VideoController'; 3 | import { SearchVideo } from '../../../src/Mooc/Videos/Application/SearchVideo'; 4 | import { CreateVideo } from '../../../src/Mooc/Videos/Application/createVideo'; 5 | import { Video } from '../../../src/Mooc/Videos/Domain/Video'; 6 | import { VideoId } from '../../../src/Mooc/Videos/Domain/VideoId'; 7 | import { CreateVideoDTO } from '../../../src/Mooc/Videos/Domain/CreateVideoDTO'; 8 | 9 | describe.skip('VideoController', () => { 10 | let videoController: VideoController; 11 | const mockVideo = new Video('2', 'abc', 60); 12 | /* This mock represents the MemoryVideoRepository, that is used by the controller 13 | in its injected application services: CreateVideo and SearchVideo */ 14 | const memoryRepoMock: jest.Mock = jest.fn((): {} => ({ 15 | createVideo: (video: Video): void => { 16 | return; 17 | }, 18 | searchVideo: (videoId: VideoId): Video => { 19 | if(videoId === '2'){ 20 | return mockVideo 21 | } 22 | return undefined; 23 | } 24 | })); 25 | 26 | beforeEach(async () => { 27 | const app: TestingModule = await Test.createTestingModule({ 28 | controllers: [VideoController], 29 | providers: [SearchVideo, CreateVideo, { 30 | provide: 'IVideoRepository', //I inject the dependency manually here 31 | useClass: memoryRepoMock 32 | }], 33 | }).compile(); 34 | 35 | videoController = app.get(VideoController); 36 | }); 37 | 38 | describe('getVideo()', () => { 39 | it('should return undefined if we try to GET a video that does not exist', () => { 40 | expect(videoController.getVideo('1')).toBe(undefined); 41 | }); 42 | 43 | it('should return a valid Video if we try to GET a video that exists', () => { 44 | expect(videoController.getVideo('2')).toEqual(mockVideo); 45 | }); 46 | }); 47 | 48 | describe('create()', () => { 49 | it('should create a valid video that is passed via POST', () => { 50 | //Arrange 51 | const dtoCreate = new CreateVideoDTO('1', 'abc', 60) 52 | //Act + Assert 53 | expect(videoController.create(dtoCreate)).toBe(undefined); 54 | }); 55 | 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /GildedRose/app/gilded-rose.ts: -------------------------------------------------------------------------------- 1 | export class Item { 2 | name: string; 3 | sellIn: number; 4 | quality: number; 5 | 6 | constructor(name, sellIn, quality) { 7 | this.name = name; 8 | this.sellIn = sellIn; 9 | this.quality = quality; 10 | } 11 | } 12 | 13 | export class GildedRose { 14 | items: Array; 15 | 16 | constructor(items = [] as Array) { 17 | this.items = items; 18 | } 19 | 20 | updateQuality() { 21 | for (let i = 0; i < this.items.length; i++) { 22 | if (this.items[i].name != 'Aged Brie' && this.items[i].name != 'Backstage passes to a TAFKAL80ETC concert') { 23 | if (this.items[i].quality > 0) { 24 | if (this.items[i].name != 'Sulfuras, Hand of Ragnaros') { 25 | this.items[i].quality = this.items[i].quality - 1 26 | } 27 | } 28 | } else { 29 | if (this.items[i].quality < 50) { 30 | this.items[i].quality = this.items[i].quality + 1 31 | if (this.items[i].name == 'Backstage passes to a TAFKAL80ETC concert') { 32 | if (this.items[i].sellIn < 11) { 33 | if (this.items[i].quality < 50) { 34 | this.items[i].quality = this.items[i].quality + 1 35 | } 36 | } 37 | if (this.items[i].sellIn < 6) { 38 | if (this.items[i].quality < 50) { 39 | this.items[i].quality = this.items[i].quality + 1 40 | } 41 | } 42 | } 43 | } 44 | } 45 | if (this.items[i].name != 'Sulfuras, Hand of Ragnaros') { 46 | this.items[i].sellIn = this.items[i].sellIn - 1; 47 | } 48 | if (this.items[i].sellIn < 0) { 49 | if (this.items[i].name != 'Aged Brie') { 50 | if (this.items[i].name != 'Backstage passes to a TAFKAL80ETC concert') { 51 | if (this.items[i].quality > 0) { 52 | if (this.items[i].name != 'Sulfuras, Hand of Ragnaros') { 53 | this.items[i].quality = this.items[i].quality - 1 54 | } 55 | } 56 | } else { 57 | this.items[i].quality = this.items[i].quality - this.items[i].quality 58 | } 59 | } else { 60 | if (this.items[i].quality < 50) { 61 | this.items[i].quality = this.items[i].quality + 1 62 | } 63 | } 64 | } 65 | } 66 | 67 | return this.items; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /typescript-ddd-example/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 6 | [circleci-url]: https://circleci.com/gh/nestjs/nest 7 | 8 |

A progressive Node.js framework for building efficient and scalable server-side applications.

9 |

10 | NPM Version 11 | Package License 12 | NPM Downloads 13 | CircleCI 14 | Coverage 15 | Discord 16 | Backers on Open Collective 17 | Sponsors on Open Collective 18 | 19 | Support us 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ npm install 33 | ``` 34 | 35 | ## Running the app 36 | 37 | ```bash 38 | # development 39 | $ npm run start 40 | 41 | # watch mode 42 | $ npm run start:dev 43 | 44 | # production mode 45 | $ npm run start:prod 46 | ``` 47 | 48 | ## Test 49 | 50 | ```bash 51 | # unit tests 52 | $ npm run test 53 | 54 | # e2e tests 55 | $ npm run test:e2e 56 | 57 | # test coverage 58 | $ npm run test:cov 59 | ``` 60 | 61 | ## Support 62 | 63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 64 | 65 | ## Stay in touch 66 | 67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 68 | - Website - [https://nestjs.com](https://nestjs.com/) 69 | - Twitter - [@nestframework](https://twitter.com/nestframework) 70 | 71 | ## License 72 | 73 | Nest is [MIT licensed](LICENSE). 74 | -------------------------------------------------------------------------------- /typescript-ddd-cqrs-example/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 6 | [circleci-url]: https://circleci.com/gh/nestjs/nest 7 | 8 |

A progressive Node.js framework for building efficient and scalable server-side applications.

9 |

10 | NPM Version 11 | Package License 12 | NPM Downloads 13 | CircleCI 14 | Coverage 15 | Discord 16 | Backers on Open Collective 17 | Sponsors on Open Collective 18 | 19 | Support us 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ npm install 33 | ``` 34 | 35 | ## Running the app 36 | 37 | ```bash 38 | # development 39 | $ npm run start 40 | 41 | # watch mode 42 | $ npm run start:dev 43 | 44 | # production mode 45 | $ npm run start:prod 46 | ``` 47 | 48 | ## Test 49 | 50 | ```bash 51 | # unit tests 52 | $ npm run test 53 | 54 | # e2e tests 55 | $ npm run test:e2e 56 | 57 | # test coverage 58 | $ npm run test:cov 59 | ``` 60 | 61 | ## Support 62 | 63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 64 | 65 | ## Stay in touch 66 | 67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 68 | - Website - [https://nestjs.com](https://nestjs.com/) 69 | - Twitter - [@nestframework](https://twitter.com/nestframework) 70 | 71 | ## License 72 | 73 | Nest is [MIT licensed](LICENSE). 74 | --------------------------------------------------------------------------------