Repository: shekohex/nest-router Branch: master Commit: 78d907258e8e Files: 85 Total size: 48.3 KB Directory structure: gitextract_4e6hp759/ ├── .gitignore ├── .npmignore ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples/ │ ├── nest-v4x/ │ │ ├── index.js │ │ ├── jest.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.module.ts │ │ │ ├── cats/ │ │ │ │ ├── cats.controller.ts │ │ │ │ ├── cats.module.ts │ │ │ │ └── ketty.controller.ts │ │ │ ├── dogs/ │ │ │ │ ├── dogs.controller.ts │ │ │ │ ├── dogs.module.ts │ │ │ │ └── puppy.controller.ts │ │ │ ├── main.ts │ │ │ ├── ninja/ │ │ │ │ ├── katana.controller.ts │ │ │ │ ├── ninja.controller.ts │ │ │ │ └── ninja.module.ts │ │ │ └── routes.ts │ │ ├── tsconfig.json │ │ └── tslint.json │ ├── nest-v5x/ │ │ ├── nodemon.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── app.module.ts │ │ │ ├── cats/ │ │ │ │ ├── cats.controller.ts │ │ │ │ ├── cats.module.ts │ │ │ │ ├── dto/ │ │ │ │ │ └── create-cat.dto.ts │ │ │ │ └── ketty.controller.ts │ │ │ ├── dogs/ │ │ │ │ ├── dogs.controller.ts │ │ │ │ ├── dogs.module.ts │ │ │ │ └── puppy.controller.ts │ │ │ ├── logger.middleware.ts │ │ │ ├── main.hmr.ts │ │ │ ├── main.ts │ │ │ ├── ninja/ │ │ │ │ ├── katana.controller.ts │ │ │ │ ├── ninja.controller.ts │ │ │ │ └── ninja.module.ts │ │ │ └── routes.ts │ │ ├── test/ │ │ │ ├── app.e2e-spec.ts │ │ │ └── jest-e2e.json │ │ ├── tsconfig.json │ │ ├── tsconfig.spec.json │ │ ├── tslint.json │ │ └── webpack.config.js │ └── nest-v5x-m2m/ │ ├── nodemon.json │ ├── package.json │ ├── src/ │ │ ├── app.module.ts │ │ ├── cats/ │ │ │ ├── cats.controller.ts │ │ │ ├── cats.module.ts │ │ │ ├── dto/ │ │ │ │ └── create-cat.dto.ts │ │ │ └── ketty.controller.ts │ │ ├── dogs/ │ │ │ ├── dogs.controller.ts │ │ │ ├── dogs.module.ts │ │ │ └── puppy.controller.ts │ │ ├── logger.middleware.ts │ │ ├── main.hmr.ts │ │ ├── main.ts │ │ ├── ninja/ │ │ │ ├── katana.controller.ts │ │ │ ├── ninja.controller.ts │ │ │ └── ninja.module.ts │ │ └── routes.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tslint.json │ └── webpack.config.js ├── jest.json ├── package.json ├── scripts/ │ └── build.sh ├── src/ │ ├── index.ts │ ├── router.module.ts │ ├── routes.interface.ts │ ├── test/ │ │ ├── router.module.spec.ts │ │ └── utils/ │ │ ├── flat-routes.spec.ts │ │ └── validate-path.spec.ts │ └── utils/ │ ├── flat-routes.util.ts │ └── validate-path.util.ts ├── tsconfig.json ├── tsconfig.prod.json ├── tslint.json └── wallaby.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components #ide .vscode .idea # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # build and Play play # remove build or lib from repo lib dist ================================================ FILE: .npmignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components #ide .vscode .idea # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # NPM src example gulpfile.js .prettierrc .travis.yml ================================================ FILE: .prettierrc ================================================ { "semi": true, "useTabs": false, "singleQuote": true, "printWidth": 100, "trailingComma": "all" } ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "8" - "9" - "10" before_script: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start install: - npm install - npm run build script: - npm test after_success: - npm run test:coverage - "npm install coveralls && cat ./coverage/lcov.info | coveralls" ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [1.0.9] ## Bugs * Fix Bug [#41](https://github.com/shekohex/nest-router/issues/41) ## [1.0.8] ### Added * Resolve Full Controller Path from anywhere ([#32](https://github.com/shekohex/nest-router/pull/32)) ## Changed * Update Dev Dependencies ## [1.0.7] - 2018-09-28 ### Changed * Project dependency refactor * add node v10 as a test target ## [1.0.6] - 2018-06-20 ### Changed * Now Nest Router Module Using Nest V5+ > See examples folder, there is `nest-v5x`. ## [1.0.5] - 2018-02-27 ### Deprecated * `childrens`, use `children` instead. see [why](https://github.com/shekohex/nest-router/issues/6)? ## [1.0.4] - 2018-02-12 ### Added * You can now Omit the `module` keyword and just using an arry of `children` and one `path` proparty. ## [1.0.3] - 2018-02-10 ### Added * `children` array can be array with just modules. this means you can omit the `path` keyword. * Unreleased section to gather unreleased changes and encourage note keeping prior to releases. ## [1.0.2] - 2018-02-08 ### Added * Routes now can be endless nested array. ## [1.0.1] - 2018-02-05 ### Changed * `children` now an Array insted of `object` ## [1.0.0] - 2018-02-05 * Published to NPM :rocket: * add continuous integration "Travis CI" ## [0.0.0] - 2018-01-31 ### Added * Greenkeeper badge * README * Good examples and basic guidelines, in example folder and README. * Build status badge ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 Shady Khalifa Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Nest Router :vertical_traffic_light: [![Greenkeeper badge](https://badges.greenkeeper.io/shekohex/nest-router.svg)](https://greenkeeper.io/) [![Build Status](https://travis-ci.org/shekohex/nest-router.svg?branch=master)](https://travis-ci.org/shekohex/nest-router) [![npm version](https://badge.fury.io/js/nest-router.svg)](Https://www.npmjs.com/package/nest-router) [![Coverage Status](https://coveralls.io/repos/github/shekohex/nest-router/badge.svg?branch=master)](https://coveralls.io/github/shekohex/nest-router?branch=master) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fshekohex%2Fnest-router.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fshekohex%2Fnest-router?ref=badge_shield) Router Module For [Nestjs](https://github.com/nestjs/nest) Framework ## Important Note As of Nestjs `v8.0.0` This module got added into the `@nestjs/core`. see the [docs](https://docs.nestjs.com/recipes/router-module) with that being said, this package is still maintained (for now). ## Quick Overview `RouterModule` helps you organize your routes and lets you create a routes tree. ### How ? Every module could have a path property. That path will be a prefix for all controllers in this module. If that module has a parent, it will be a child of it and again all controllers in this child module will be prefixed by `parent module prefix` + `this module prefix` > see issue [#255](https://github.com/nestjs/nest/issues/255) . ## Install IMPORTANT: you need Nest > v4.5.10+ ```bash npm install nest-router --save ``` OR ```bash yarn add nest-router ``` ## Setup See how easy it is to set up. ```ts ... //imports const routes: Routes = [ { path: '/ninja', module: NinjaModule, children: [ { path: '/cats', module: CatsModule, }, { path: '/dogs', module: DogsModule, }, ], }, ]; @Module({ imports: [ RouterModule.forRoutes(routes), // setup the routes CatsModule, DogsModule, NinjaModule ], // as usual, nothing new }) export class ApplicationModule {} ``` > :+1: TIP: Keep all of your routes in a separate file like `routes.ts` In this example, all the controllers in `NinjaModule` will be prefixed by `/ninja` and it has two childs, `CatsModule` and `DogsModule`. Will the controllers of `CatsModule` be prefixed by `/cats`? NO!! :open_mouth: The `CatsModule` is a child of `NinjaModule` so it will be prefixed by `/ninja/cats/` instead. And so will `DogsModule`. > See examples folder for more information. #### Example Folder Project Structure ```bash . ├── app.module.ts ├── cats │   ├── cats.controller.ts │   ├── cats.module.ts │   └── ketty.controller.ts ├── dogs │   ├── dogs.controller.ts │   ├── dogs.module.ts │   └── puppy.controller.ts ├── main.ts └── ninja ├── katana.controller.ts ├── ninja.controller.ts └── ninja.module.ts ``` And here is a simple, nice route tree of `example` folder: ```bash ninja ├── / ├── /katana ├── cats │   ├── / │   └── /ketty ├── dogs    ├── /    └── /puppy ``` Nice! #### Params in nested routes In a standard REST API, you probably would need to add some params to your nested routes. Here is an example of how you can achieve it: ```ts ... //imports const routes: Routes = [ { path: '/ninja', module: NinjaModule, children: [ { path: '/:ninjaId/cats', module: CatsModule, }, { path: '/:ninjaId/dogs', module: DogsModule, }, ], }, ]; ``` The `ninjaId` param will be available inside `CatsModule` controllers and `DogsModule` controllers. Please, find the [instruction how to handle params in the official documentation](https://docs.nestjs.com/controllers#route-parameters). It might be a good practice to use a [pipe for transformation use case](https://docs.nestjs.com/pipes#transformation-use-case) to have an access to `ninja` object instead of just id. #### Resolve Full Controller Path: Nestjs dosen't resolve or take into account `MODULE_PATH` metadata when it is coming to resolve Controller path in Middleware resolver for example, so that i introduced a new fancy method `RouterModule#resolvePath` that will resolve the full path of any controller so instead of doing so: ```ts consumer.apply(someMiddleware).forRoutes(SomeController); ``` you should do ```ts consumer.apply(someMiddleware).forRoutes(RouterModule.resolvePath(SomeController)); ``` see [#32](https://github.com/shekohex/nest-router/pull/32) for more information about this. ## CHANGELOG See [CHANGELOG](CHANGELOG.md) for more information. ## Contributing You are welcome to contribute to this project, just open a PR. ## Authors * **Shady Khalifa** - _Initial work_ See also the list of [contributors](https://github.com/shekohex/nest-router/contributors) who participated in this project. ## License This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fshekohex%2Fnest-router.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fshekohex%2Fnest-router?ref=badge_large) ================================================ FILE: examples/nest-v4x/index.js ================================================ require('ts-node/register'); require('./src/main'); ================================================ FILE: examples/nest-v4x/jest.json ================================================ { "moduleFileExtensions": [ "ts", "tsx", "js", "json" ], "transform": { "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js" }, "testRegex": "/src/.*\\.(test|spec).(ts|tsx|js)$", "collectCoverageFrom" : ["src/**/*.{js,jsx,tsx,ts}", "!**/node_modules/**", "!**/vendor/**"], "coverageReporters": ["json", "lcov"] } ================================================ FILE: examples/nest-v4x/package.json ================================================ { "name": "nest-router-example-v4x", "version": "1.0.0", "description": "Nest TypeScript starter repository", "license": "MIT", "scripts": { "start": "node index.js", "prestart:prod": "tsc", "start:prod": "node dist/main.js", "test": "jest --config=jest.json", "test:watch": "jest --watch --config=jest.json", "test:coverage": "jest --config=jest.json --coverage --coverageDirectory=coverage", "e2e": "jest --config=e2e/jest-e2e.json --forceExit", "e2e:watch": "jest --watch --config=e2e/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^4.6.4", "@nestjs/core": "^4.6.4", "@nestjs/testing": "^4.6.1", "class-transformer": "^0.1.7", "class-validator": "^0.7.2", "nest-router": "^1.0.5", "reflect-metadata": "^0.1.10", "rxjs": "^5.4.3", "typescript": "^2.4.2" }, "devDependencies": { "@types/jest": "^20.0.8", "@types/node": "^7.0.41", "jest": "^20.0.4", "supertest": "^3.0.0", "ts-jest": "^20.0.14", "ts-node": "^3.3.0" } } ================================================ FILE: examples/nest-v4x/src/app.module.ts ================================================ import { Module, NestModule, MiddlewaresConsumer, RequestMethod } from '@nestjs/common'; import { RouterModule } from 'nest-router'; import { CatsModule } from './cats/cats.module'; import { DogsModule } from './dogs/dogs.module'; import { NinjaModule } from './ninja/ninja.module'; import { routes } from './routes'; @Module({ imports: [RouterModule.forRoutes(routes), CatsModule, DogsModule, NinjaModule], }) export class ApplicationModule {} ================================================ FILE: examples/nest-v4x/src/cats/cats.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller() export class CatsController { @Get('/') sayHello() { return `Hello From CatsController`; } } ================================================ FILE: examples/nest-v4x/src/cats/cats.module.ts ================================================ import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { KettyController } from './ketty.controller'; @Module({ controllers: [CatsController, KettyController], }) export class CatsModule {} ================================================ FILE: examples/nest-v4x/src/cats/ketty.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller('/ketty') export class KettyController { @Get('/') sayHello() { return `Hello From KettyController`; } } ================================================ FILE: examples/nest-v4x/src/dogs/dogs.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller() export class DogsController { @Get('/') sayHello() { return `Hello From DogsController`; } } ================================================ FILE: examples/nest-v4x/src/dogs/dogs.module.ts ================================================ import { Module } from '@nestjs/common'; import { DogsController } from './dogs.controller'; import { PuppyController } from './puppy.controller'; @Module({ controllers: [DogsController, PuppyController], }) export class DogsModule {} ================================================ FILE: examples/nest-v4x/src/dogs/puppy.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller('/puppy') export class PuppyController { @Get('/') sayHello() { return `Hello From PuppyController`; } } ================================================ FILE: examples/nest-v4x/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { ApplicationModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(ApplicationModule); await app.listen(3000); } bootstrap(); ================================================ FILE: examples/nest-v4x/src/ninja/katana.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller('/katana') export class KatanaController { @Get('/') sayHello() { return `Hello From KatanaController`; } } ================================================ FILE: examples/nest-v4x/src/ninja/ninja.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller() export class NinjaController { @Get('/') sayHello() { return `Hello From NinjaController`; } } ================================================ FILE: examples/nest-v4x/src/ninja/ninja.module.ts ================================================ import { Module } from '@nestjs/common'; import { NinjaController } from './ninja.controller'; import { KatanaController } from './katana.controller'; @Module({ controllers: [NinjaController, KatanaController], }) export class NinjaModule {} ================================================ FILE: examples/nest-v4x/src/routes.ts ================================================ import { Routes } from 'nest-router'; import { CatsModule } from './cats/cats.module'; import { DogsModule } from './dogs/dogs.module'; import { NinjaModule } from './ninja/ninja.module'; export const routes: Routes = [ { path: '/ninja', module: NinjaModule, children: [{ path: '/cats', module: CatsModule }, { path: '/dogs', module: DogsModule }], }, ]; ================================================ FILE: examples/nest-v4x/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": false, "noImplicitAny": false, "removeComments": true, "noLib": false, "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es6", "sourceMap": true, "allowJs": true, "outDir": "./dist" }, "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ] } ================================================ FILE: examples/nest-v4x/tslint.json ================================================ { "defaultSeverity": "error", "extends": [ "tslint:recommended" ], "jsRules": { "no-unused-expression": true }, "rules": { "eofline": false, "quotemark": [ true, "single" ], "ordered-imports": [ false ], "max-line-length": [ 150 ], "member-ordering": [ false ], "curly": false, "interface-name": [ false ], "array-type": [ false ], "member-access": [ false ], "no-empty-interface": false, "no-empty": false, "arrow-parens": false, "object-literal-sort-keys": false, "no-unused-expression": false, "max-classes-per-file": [ false ], "variable-name": [ false ], "one-line": [ false ], "one-variable-per-declaration": [ false ] }, "rulesDirectory": [] } ================================================ FILE: examples/nest-v5x/nodemon.json ================================================ { "watch": ["src"], "ext": "ts", "ignore": ["src/**/*.spec.ts"], "exec": "ts-node -r tsconfig-paths/register src/main.ts" } ================================================ FILE: examples/nest-v5x/package.json ================================================ { "name": "nest-router-example-v5x", "version": "1.0.0", "description": "Nest TypeScript starter repository", "license": "MIT", "scripts": { "format": "prettier --write \"src/**/*.ts\"", "start": "ts-node -r tsconfig-paths/register src/main.ts", "start:dev": "nodemon", "prestart:prod": "rm -rf dist && tsc", "start:prod": "node dist/main.js", "start:hmr": "node dist/server", "lint": "tslint -p tsconfig.json -c tslint.json", "test": "jest", "test:cov": "jest --coverage", "test:e2e": "jest --config ./test/jest-e2e.json", "webpack": "webpack --config webpack.config.js" }, "dependencies": { "@nestjs/common": "^5.5.0", "@nestjs/core": "^5.5.0", "@nestjs/microservices": "^5.5.0", "@nestjs/testing": "^5.5.0", "@nestjs/websockets": "^5.5.0", "class-transformer": "^0.2.0", "class-validator": "^0.9.1", "nest-router": "^1.0.8", "reflect-metadata": "^0.1.12", "rxjs": "^6.3.0", "typescript": "^2.9.2" }, "devDependencies": { "@types/express": "^4.0.39", "@types/jest": "^21.1.8", "@types/node": "^9.3.0", "@types/supertest": "^2.0.4", "jest": "^21.2.1", "nodemon": "^1.14.1", "prettier": "^1.11.1", "supertest": "^3.0.0", "ts-jest": "^21.2.4", "ts-loader": "^4.1.0", "ts-node": "^4.1.0", "tsconfig-paths": "^3.1.1", "tslint": "5.3.2", "webpack": "^4.2.0", "webpack-cli": "^5.0.1", "webpack-node-externals": "^1.6.0" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "coverageDirectory": "../coverage" } } ================================================ FILE: examples/nest-v5x/src/app.module.ts ================================================ import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { RouterModule, Route } from 'nest-router'; import { CatsModule } from './cats/cats.module'; import { DogsModule } from './dogs/dogs.module'; import { NinjaModule } from './ninja/ninja.module'; import { routes } from './routes'; import { LoggerMiddleware } from './logger.middleware'; @Module({ imports: [RouterModule.forRoutes(routes), CatsModule, DogsModule, NinjaModule], }) export class ApplicationModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .with({ path: '/ninja' } as Route) .forRoutes('/'); } } ================================================ FILE: examples/nest-v5x/src/cats/cats.controller.ts ================================================ import { Controller, Get, Post, Body, ValidationPipe } from '@nestjs/common'; import { CreateCatDTO } from './dto/create-cat.dto'; @Controller() export class CatsController { private cats: string[] = []; @Get('/') sayHello() { return `Hello From CatsController`; } // For testing Pipes @Post('/create') public testing(@Body(new ValidationPipe()) data: CreateCatDTO): string[] { this.cats.push(data.name); return this.cats; } } ================================================ FILE: examples/nest-v5x/src/cats/cats.module.ts ================================================ import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { KettyController } from './ketty.controller'; @Module({ controllers: [CatsController, KettyController], }) export class CatsModule {} ================================================ FILE: examples/nest-v5x/src/cats/dto/create-cat.dto.ts ================================================ import { IsString } from 'class-validator'; export class CreateCatDTO { @IsString() public readonly name: string; } ================================================ FILE: examples/nest-v5x/src/cats/ketty.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller('/ketty') export class KettyController { @Get('/') sayHello() { return `Hello From KettyController`; } } ================================================ FILE: examples/nest-v5x/src/dogs/dogs.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller() export class DogsController { @Get('/') sayHello() { return `Hello From DogsController`; } } ================================================ FILE: examples/nest-v5x/src/dogs/dogs.module.ts ================================================ import { Module } from '@nestjs/common'; import { DogsController } from './dogs.controller'; import { PuppyController } from './puppy.controller'; @Module({ controllers: [DogsController, PuppyController], }) export class DogsModule {} ================================================ FILE: examples/nest-v5x/src/dogs/puppy.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller('/puppy') export class PuppyController { @Get('/') sayHello() { return `Hello From PuppyController`; } } ================================================ FILE: examples/nest-v5x/src/logger.middleware.ts ================================================ import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common'; import { Routes } from 'nest-router'; @Injectable() export class LoggerMiddleware implements NestMiddleware { resolve(...excluded: Routes): MiddlewareFunction { return (req, _res, next) => { const isExcluded = excluded.filter(route => { console.log(LoggerMiddleware.name, ':', '{ Request Path ->', req.path, ' }'); const excludePath = route.path === req.path; return excludePath; }).length > 0; console.log(LoggerMiddleware.name, ':', '{ Is Excluded ->', isExcluded, ' }'); next(); }; } } ================================================ FILE: examples/nest-v5x/src/main.hmr.ts ================================================ import { NestFactory } from '@nestjs/core'; import { ApplicationModule } from './app.module'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(ApplicationModule); await app.listen(3000); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap(); ================================================ FILE: examples/nest-v5x/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { ApplicationModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(ApplicationModule); await app.listen(3000); } bootstrap(); ================================================ FILE: examples/nest-v5x/src/ninja/katana.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller('/katana') export class KatanaController { @Get('/') sayHello() { return `Hello From KatanaController`; } } ================================================ FILE: examples/nest-v5x/src/ninja/ninja.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller() export class NinjaController { @Get('/') sayHello() { return `Hello From NinjaController`; } } ================================================ FILE: examples/nest-v5x/src/ninja/ninja.module.ts ================================================ import { Module } from '@nestjs/common'; import { NinjaController } from './ninja.controller'; import { KatanaController } from './katana.controller'; @Module({ controllers: [NinjaController, KatanaController], }) export class NinjaModule {} ================================================ FILE: examples/nest-v5x/src/routes.ts ================================================ import { Routes } from 'nest-router'; import { CatsModule } from './cats/cats.module'; import { DogsModule } from './dogs/dogs.module'; import { NinjaModule } from './ninja/ninja.module'; export const routes: Routes = [ { path: '/ninja', module: NinjaModule, children: [{ path: '/cats', module: CatsModule }, { path: '/dogs', module: DogsModule }], }, ]; ================================================ FILE: examples/nest-v5x/test/app.e2e-spec.ts ================================================ import request from 'supertest'; import { Test } from '@nestjs/testing'; import { AppModule } from './../src/app.module'; import { INestApplication } from '@nestjs/common'; describe('AppController (e2e)', () => { let app: INestApplication; beforeAll(async () => { const moduleFixture = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/GET /', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: examples/nest-v5x/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: examples/nest-v5x/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "noImplicitAny": false, "removeComments": true, "noLib": false, "allowSyntheticDefaultImports": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es6", "sourceMap": true, "outDir": "./dist", "baseUrl": "./src" }, "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ] } ================================================ FILE: examples/nest-v5x/tsconfig.spec.json ================================================ { "extends": "tsconfig.json", "compilerOptions": { "types": ["jest", "node"] }, "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: examples/nest-v5x/tslint.json ================================================ { "defaultSeverity": "error", "extends": ["tslint:recommended"], "jsRules": { "no-unused-expression": true }, "rules": { "eofline": false, "quotemark": [true, "single"], "indent": false, "member-access": [false], "ordered-imports": [false], "max-line-length": [true, 150], "member-ordering": [false], "curly": false, "interface-name": [false], "array-type": [false], "no-empty-interface": false, "no-empty": false, "arrow-parens": false, "object-literal-sort-keys": false, "no-unused-expression": false, "no-console": false, "variable-name": [false], "one-line": [false], "one-variable-per-declaration": [false] }, "rulesDirectory": [] } ================================================ FILE: examples/nest-v5x/webpack.config.js ================================================ const webpack = require('webpack'); const path = require('path'); const nodeExternals = require('webpack-node-externals'); module.exports = { entry: ['webpack/hot/poll?1000', './src/main.hmr.ts'], watch: true, target: 'node', externals: [ nodeExternals({ whitelist: ['webpack/hot/poll?1000'], }), ], module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, mode: "development", resolve: { extensions: ['.tsx', '.ts', '.js'], }, plugins: [ new webpack.HotModuleReplacementPlugin(), ], output: { path: path.join(__dirname, 'dist'), filename: 'server.js', }, }; ================================================ FILE: examples/nest-v5x-m2m/nodemon.json ================================================ { "watch": ["src"], "ext": "ts", "ignore": ["src/**/*.spec.ts"], "exec": "ts-node -r tsconfig-paths/register src/main.ts" } ================================================ FILE: examples/nest-v5x-m2m/package.json ================================================ { "name": "nest-router-example-v5x-m2m", "version": "1.0.0", "description": "Nest TypeScript starter repository", "license": "MIT", "scripts": { "format": "prettier --write \"src/**/*.ts\"", "start": "ts-node -r tsconfig-paths/register src/main.ts", "start:dev": "nodemon", "prestart:prod": "rm -rf dist && tsc", "start:prod": "node dist/main.js", "start:hmr": "node dist/server", "lint": "tslint -p tsconfig.json -c tslint.json", "test": "jest", "test:cov": "jest --coverage", "test:e2e": "jest --config ./test/jest-e2e.json", "webpack": "webpack --config webpack.config.js" }, "dependencies": { "@nestjs/common": "^5.5.0", "@nestjs/core": "^5.5.0", "@nestjs/microservices": "^5.5.0", "@nestjs/testing": "^5.5.0", "@nestjs/websockets": "^5.5.0", "class-transformer": "^0.2.0", "class-validator": "^0.9.1", "nest-router": "^1.0.9", "reflect-metadata": "^0.1.12", "rxjs": "^6.3.0", "typescript": "^2.9.2" }, "devDependencies": { "@types/express": "^4.0.39", "@types/jest": "^21.1.8", "@types/node": "^9.3.0", "@types/supertest": "^2.0.4", "jest": "^21.2.1", "nodemon": "^1.14.1", "prettier": "^1.11.1", "supertest": "^3.0.0", "ts-jest": "^21.2.4", "ts-loader": "^4.1.0", "ts-node": "^4.1.0", "tsconfig-paths": "^3.1.1", "tslint": "5.3.2", "webpack": "^4.28.0", "webpack-cli": "^3.1.2", "webpack-node-externals": "^1.7.2" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "coverageDirectory": "../coverage" } } ================================================ FILE: examples/nest-v5x-m2m/src/app.module.ts ================================================ import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { RouterModule, Route } from 'nest-router'; import { CatsModule } from './cats/cats.module'; import { DogsModule } from './dogs/dogs.module'; import { NinjaModule } from './ninja/ninja.module'; import { routes } from './routes'; import { LoggerMiddleware } from './logger.middleware'; @Module({ imports: [RouterModule.forRoutes(routes), CatsModule, DogsModule, NinjaModule], }) export class ApplicationModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .with({ path: '/ninja' } as Route) .forRoutes('/'); } } ================================================ FILE: examples/nest-v5x-m2m/src/cats/cats.controller.ts ================================================ import { Controller, Get, Post, Body, ValidationPipe, Param } from '@nestjs/common'; import { CreateCatDTO } from './dto/create-cat.dto'; @Controller() export class CatsController { private cats: string[] = []; @Get('/') sayHello() { return `Hello From CatsController`; } @Get('/parent') whatIsTheNinjaId(@Param('ninjaId') ninjaId: string) { return `Hello From CatsController the ninjeId: ${ninjaId}`; } @Get('/:catId') getCat(@Param('catId') id: string) { const idx = parseInt(id, 10) || 0; return { cat: this.cats[idx - 1] || null }; } // For testing Pipes @Post('/create') public testing(@Body(new ValidationPipe()) data: CreateCatDTO): string[] { this.cats.push(data.name); return this.cats; } } ================================================ FILE: examples/nest-v5x-m2m/src/cats/cats.module.ts ================================================ import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { KettyController } from './ketty.controller'; @Module({ controllers: [CatsController, KettyController], }) export class CatsModule {} ================================================ FILE: examples/nest-v5x-m2m/src/cats/dto/create-cat.dto.ts ================================================ import { IsString } from 'class-validator'; export class CreateCatDTO { @IsString() public readonly name: string; } ================================================ FILE: examples/nest-v5x-m2m/src/cats/ketty.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller('/ketty') export class KettyController { @Get('/') sayHello() { return `Hello From KettyController`; } } ================================================ FILE: examples/nest-v5x-m2m/src/dogs/dogs.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller() export class DogsController { @Get('/') sayHello() { return `Hello From DogsController`; } } ================================================ FILE: examples/nest-v5x-m2m/src/dogs/dogs.module.ts ================================================ import { Module } from '@nestjs/common'; import { DogsController } from './dogs.controller'; import { PuppyController } from './puppy.controller'; @Module({ controllers: [DogsController, PuppyController], }) export class DogsModule {} ================================================ FILE: examples/nest-v5x-m2m/src/dogs/puppy.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller('/puppy') export class PuppyController { @Get('/') sayHello() { return `Hello From PuppyController`; } } ================================================ FILE: examples/nest-v5x-m2m/src/logger.middleware.ts ================================================ import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common'; import { Routes } from 'nest-router'; @Injectable() export class LoggerMiddleware implements NestMiddleware { resolve(...excluded: Routes): MiddlewareFunction { return (req, _res, next) => { const isExcluded = excluded.filter(route => { console.log(LoggerMiddleware.name, ':', '{ Request Path ->', req.path, ' }'); const excludePath = route.path === req.path; return excludePath; }).length > 0; console.log(LoggerMiddleware.name, ':', '{ Is Excluded ->', isExcluded, ' }'); next(); }; } } ================================================ FILE: examples/nest-v5x-m2m/src/main.hmr.ts ================================================ import { NestFactory } from '@nestjs/core'; import { ApplicationModule } from './app.module'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(ApplicationModule); await app.listen(3000); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap(); ================================================ FILE: examples/nest-v5x-m2m/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { ApplicationModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(ApplicationModule); await app.listen(3000); } bootstrap(); ================================================ FILE: examples/nest-v5x-m2m/src/ninja/katana.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; @Controller('/katana') export class KatanaController { @Get('/') sayHello() { return `Hello From KatanaController`; } } ================================================ FILE: examples/nest-v5x-m2m/src/ninja/ninja.controller.ts ================================================ import { Controller, Get, Param, Post, Body } from '@nestjs/common'; @Controller() export class NinjaController { private readonly ninjas: string[] = []; @Get('/') sayHello() { return `Hello From NinjaController`; } @Get('/all') getAllNinja() { return this.ninjas; } @Get('/:ninjaId') getNinja(@Param('ninjaId') id: string) { const idx = parseInt(id, 10) || 0; return { ninja: this.ninjas[idx - 1] || null }; } @Post('/create') createNinja(@Body('name') name: string) { const id = this.ninjas.push(name); return { ninja: this.ninjas[id - 1] || null }; } } ================================================ FILE: examples/nest-v5x-m2m/src/ninja/ninja.module.ts ================================================ import { Module } from '@nestjs/common'; import { NinjaController } from './ninja.controller'; import { KatanaController } from './katana.controller'; @Module({ controllers: [NinjaController, KatanaController], }) export class NinjaModule {} ================================================ FILE: examples/nest-v5x-m2m/src/routes.ts ================================================ import { Routes } from 'nest-router'; import { CatsModule } from './cats/cats.module'; import { DogsModule } from './dogs/dogs.module'; import { NinjaModule } from './ninja/ninja.module'; export const routes: Routes = [ { path: '/ninja', module: NinjaModule, children: [ { path: 'nested/cats', module: CatsModule }, { path: ':ninjaId/cats', module: CatsModule }, { path: '/dogs', module: DogsModule }, ], }, ]; ================================================ FILE: examples/nest-v5x-m2m/test/app.e2e-spec.ts ================================================ import request from 'supertest'; import { Test } from '@nestjs/testing'; import { AppModule } from './../src/app.module'; import { INestApplication } from '@nestjs/common'; describe('AppController (e2e)', () => { let app: INestApplication; beforeAll(async () => { const moduleFixture = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/GET /', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: examples/nest-v5x-m2m/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: examples/nest-v5x-m2m/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "noImplicitAny": false, "removeComments": true, "noLib": false, "allowSyntheticDefaultImports": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es6", "sourceMap": true, "outDir": "./dist", "baseUrl": "./src" }, "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ] } ================================================ FILE: examples/nest-v5x-m2m/tsconfig.spec.json ================================================ { "extends": "tsconfig.json", "compilerOptions": { "types": ["jest", "node"] }, "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: examples/nest-v5x-m2m/tslint.json ================================================ { "defaultSeverity": "error", "extends": ["tslint:recommended"], "jsRules": { "no-unused-expression": true }, "rules": { "eofline": false, "quotemark": [true, "single"], "indent": false, "member-access": [false], "ordered-imports": [false], "max-line-length": [true, 150], "member-ordering": [false], "curly": false, "interface-name": [false], "array-type": [false], "no-empty-interface": false, "no-empty": false, "arrow-parens": false, "object-literal-sort-keys": false, "no-unused-expression": false, "no-console": false, "variable-name": [false], "one-line": [false], "one-variable-per-declaration": [false] }, "rulesDirectory": [] } ================================================ FILE: examples/nest-v5x-m2m/webpack.config.js ================================================ const webpack = require('webpack'); const path = require('path'); const nodeExternals = require('webpack-node-externals'); module.exports = { entry: ['webpack/hot/poll?1000', './src/main.hmr.ts'], watch: true, target: 'node', externals: [ nodeExternals({ whitelist: ['webpack/hot/poll?1000'], }), ], module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, mode: "development", resolve: { extensions: ['.tsx', '.ts', '.js'], }, plugins: [ new webpack.HotModuleReplacementPlugin(), ], output: { path: path.join(__dirname, 'dist'), filename: 'server.js', }, }; ================================================ FILE: jest.json ================================================ { "moduleFileExtensions": ["ts", "tsx", "js", "json"], "transform": { "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js" }, "testRegex": "/src/.*\\.(test|spec).(ts|tsx|js)$", "testEnvironment": "node", "collectCoverageFrom": [ "src/**/*.ts", "!src/main.ts", "!src/app/constants.ts", "!src/**/index.ts", "!src/**/*.module.ts", "!src/**/*.interface.ts", "!src/**/*.enum.ts", "!**/node_modules/**", "!**/vendor/**" ], "coverageReporters": ["json", "lcov"] } ================================================ FILE: package.json ================================================ { "name": "nest-router", "version": "1.0.9", "description": "Router Module For Nestjs Framework", "main": "index.js", "scripts": { "test": "jest --notify --config=jest.json", "test:watch": "jest --watch --config=jest.json", "test:coverage": "jest --config=jest.json --coverage --coverageDirectory=coverage", "build": "./scripts/build.sh", "build:andMove": "./scripts/build.sh andMove", "npm:publish": "cd lib && npm publish" }, "repository": { "type": "git", "url": "git+https://github.com/shekohex/nest-router.git" }, "keywords": [ "nestjs", "router", "addons" ], "author": "Shady Khalifa ", "license": "MIT", "devDependencies": { "@nestjs/common": "^5.0.0", "@nestjs/core": "^5.0.0", "@nestjs/testing": "^5.3.11", "@types/jest": "^24.0.0", "@types/node": "^12.0.0", "coveralls": "^3.0.2", "jest": "^23.6.0", "reflect-metadata": "^0.1.12", "rxjs": "^6.3.0", "ts-jest": "^24.0.0", "typescript": "^3.0.0" }, "bugs": { "url": "https://github.com/shekohex/nest-router/issues" }, "homepage": "https://github.com/shekohex/nest-router#readme" } ================================================ FILE: scripts/build.sh ================================================ #!/bin/bash # A basic script to build and compile the typescript files using tsc # Set an error handler trap onExit EXIT # printing the simple stack trace onExit() { while caller $((n++)); do :; done; } build() { echo 'Start building..' # Run tsc tsc -p tsconfig.prod.json echo 'tsc exist with status code:' $? echo 'Copying Other files..' cp -rf package.json lib cp -rf README.md lib echo 'Done.' echo '--------' } move() { echo 'Copying files to examples/**/node_modules ..' for d in examples/*; do if [ -d "$d" ]; then dist="$d"/node_modules/nest-router echo 'Start copying to' "$dist" mkdir -p "$dist" && cp -rf lib/* "$dist" fi done echo 'Done.' echo '--------' } build if [ "$1" = "andMove" ]; then move fi ================================================ FILE: src/index.ts ================================================ export * from './router.module'; export * from './routes.interface'; ================================================ FILE: src/router.module.ts ================================================ import { Module, DynamicModule } from '@nestjs/common'; import { MODULE_PATH, PATH_METADATA } from '@nestjs/common/constants'; import { ModulesContainer } from '@nestjs/core/injector/modules-container'; import { Controller, Type } from '@nestjs/common/interfaces'; import { UnknownElementException } from '@nestjs/core/errors/exceptions/unknown-element.exception'; import { validatePath } from './utils/validate-path.util'; import { flatRoutes } from './utils/flat-routes.util'; import { Routes } from './routes.interface'; /** * A utility Module to Organize your Routes, * it could be imported in the Root Module of you application. */ @Module({}) export class RouterModule { private static readonly routesContainer: Map = new Map(); constructor(readonly modulesContainer: ModulesContainer) { const modules = [...modulesContainer.values()]; for (const nestModule of modules) { const modulePath: string = Reflect.getMetadata(MODULE_PATH, nestModule.metatype); for (const route of nestModule.routes.values()) { RouterModule.routesContainer.set(route.name, validatePath(modulePath)); } } } /** * takes an array of modules and organize them in hierarchy way * @param {Routes} routes Array of Routes */ public static forRoutes(routes: Routes): DynamicModule { RouterModule.buildPathMap(routes); return { module: RouterModule, }; } /** * get the controller full route path eg: (controller's module prefix + controller's path). * @param {Type} controller the controller you need to get it's full path */ public static resolvePath(controller: Type): string { const controllerPath: string = Reflect.getMetadata(PATH_METADATA, controller); const modulePath = RouterModule.routesContainer.get(controller.name); if (modulePath && controllerPath) { return validatePath(modulePath + validatePath(controllerPath)); } else { throw new UnknownElementException(); } } private static buildPathMap(routes: Routes) { const flattenRoutes = flatRoutes(routes); flattenRoutes.forEach(route => { Reflect.defineMetadata(MODULE_PATH, validatePath(route.path), route.module); }); } } ================================================ FILE: src/routes.interface.ts ================================================ import { Type } from '@nestjs/common'; /** * Defines the Routes Tree * - `path` - a string describe the Module path which will be applied * to all it's controllers and childs * - `module` - the parent Module. * - `children` - an array of child Modules. * - `childrens` @deprecated - @see children */ export interface Route { path: string; module?: Type | string; childrens?: Routes | Type[] | string[]; children?: Routes | Type[] | string[]; } /** * Defines the Routes Tree * - `path` - a string describe the Module path which will be applied * to all it's controllers and childs * - `module` - the parent Module. * - `children` - an array of child Modules. * - `childrens` @deprecated - @see children */ export type Routes = Route[]; ================================================ FILE: src/test/router.module.spec.ts ================================================ import { RouterModule } from '../router.module'; import { Routes } from '../routes.interface'; import { Module, Controller } from '@nestjs/common'; import { MODULE_PATH } from '@nestjs/common/constants'; import { Test } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; describe('RouterModule', () => { let app: INestApplication; @Controller('/parent-controller') class ParentController {} @Controller('/child-controller') class ChildController {} @Controller('no-slash-controller') class NoSlashController {} class UnknownController {} @Module({ controllers: [ParentController] }) class ParentModule {} @Module({ controllers: [ChildController] }) class ChildModule {} @Module({}) class AuthModule {} @Module({}) class PaymentsModule {} @Module({ controllers: [NoSlashController] }) class NoSlashModule {} const routes1: Routes = [ { path: 'parent', module: ParentModule, children: [ { path: 'child', module: ChildModule, }, ], }, ]; const routes2: Routes = [{ path: 'v1', children: [AuthModule, PaymentsModule, NoSlashModule] }]; @Module({ imports: [ParentModule, ChildModule, RouterModule.forRoutes(routes1)] }) class MainModule {} @Module({ imports: [AuthModule, PaymentsModule, NoSlashModule, RouterModule.forRoutes(routes2)] }) class AppModule {} test('it should add Path Metadata to all Routes', () => { const parentPath = Reflect.getMetadata(MODULE_PATH, ParentModule); const childPath = Reflect.getMetadata(MODULE_PATH, ChildModule); expect(parentPath).toEqual('/parent'); expect(childPath).toEqual('/parent/child'); }); test('it should add paths even we omitted the module keyword', () => { const authPath = Reflect.getMetadata(MODULE_PATH, AuthModule); const paymentPath = Reflect.getMetadata(MODULE_PATH, PaymentsModule); expect(authPath).toEqual('/v1'); expect(paymentPath).toEqual('/v1'); }); describe('Full Running App', async () => { beforeAll(async () => { const module = await Test.createTestingModule({ imports: [MainModule, AppModule], }).compile(); app = module.createNestApplication(); await app.init(); }); it('should Resolve Controllers path with its Module Path if any', async () => { expect(RouterModule.resolvePath(ParentController)).toEqual('/parent/parent-controller'); expect(RouterModule.resolvePath(ChildController)).toEqual('/parent/child/child-controller'); }); it('should throw error when we cannot find the controller', async () => { expect(() => RouterModule.resolvePath(UnknownController)).toThrowError( 'Nest cannot find given element (it does not exist in current context)', ); }); it('should resolve controllers path concatinated with its module path correctly', async () => { expect(RouterModule.resolvePath(NoSlashController)).toEqual('/v1/no-slash-controller'); }); afterAll(async () => { await app.close(); }); }); }); ================================================ FILE: src/test/utils/flat-routes.spec.ts ================================================ import { flatRoutes } from '../../utils/flat-routes.util'; describe('FlatRoutes', () => { const f = flatRoutes; test('it should flat all Routes to endless, and we could also ommit the path', () => { const routes = [ { path: '/parent', module: 'ParentModule', children: [ { path: '/child', module: 'ChildModule', children: [ { path: '/child2', module: 'ChildModule2' }, { path: '/parentchild', module: 'ParentChildModule', children: [ { path: '/childchild', module: 'ChildChildModule', children: [{ path: '/child2child', module: 'ChildChildModule2' }], }, ], }, ], }, ], }, { path: '/v1', children: ['AuthModule', 'CatsModule', 'DogsModule'] }, { path: '/v2', children: ['AuthModule2', 'CatsModule2'] }, { path: '/v3', childrens: ['AuthModule3', 'CatsModule3'] }, ]; const expectedRoutes = [ { path: '/parent', module: 'ParentModule' }, { path: '/parent/child', module: 'ChildModule' }, { path: '/parent/child/child2', module: 'ChildModule2' }, { path: '/parent/child/parentchild', module: 'ParentChildModule' }, { path: '/parent/child/parentchild/childchild', module: 'ChildChildModule' }, { path: '/parent/child/parentchild/childchild/child2child', module: 'ChildChildModule2' }, { path: '/v1', module: 'AuthModule' }, { path: '/v1', module: 'CatsModule' }, { path: '/v1', module: 'DogsModule' }, { path: '/v2', module: 'AuthModule2' }, { path: '/v2', module: 'CatsModule2' }, { path: '/v3', module: 'AuthModule3' }, { path: '/v3', module: 'CatsModule3' }, ]; expect(f(routes)).toEqual(expectedRoutes); }); }); ================================================ FILE: src/test/utils/validate-path.spec.ts ================================================ import { validatePath } from '../../utils/validate-path.util'; describe('ValidatePath', () => { const v = validatePath; test('it should add a / if it dose not exist', () => { expect(v('')).toEqual('/'); expect(v('path')).toEqual('/path'); }); test('it should remove all trailing slashes at the end of the path', () => { expect(v('path/')).toEqual('/path'); expect(v('path///')).toEqual('/path'); expect(v('/path/path///')).toEqual('/path/path'); }); test('it should replace all slashes with only one slash', () => { expect(v('////path/')).toEqual('/path'); expect(v('///')).toEqual('/'); expect(v('/path////path///')).toEqual('/path/path'); }); }); ================================================ FILE: src/utils/flat-routes.util.ts ================================================ import { isString } from 'util'; import { Routes } from '../routes.interface'; import { validatePath } from './validate-path.util'; const result = []; export function flatRoutes(routes: Routes) { routes.forEach(element => { if (element.module && element.path) { result.push(element); } // this block will be removed soon if (!element.children && element.childrens) { element.children = element.childrens; console.log( `\x1b[33m%s\x1b[0m`, `WARNING: 'childrens' is deprecated, use 'children' instead.`, ); } if (element.children) { const childrenRef = element.children as Routes; childrenRef.forEach(child => { if (!isString(child) && child.path) { child.path = validatePath(validatePath(element.path) + validatePath(child.path)); } else { result.push({ path: element.path, module: child }); } }); return flatRoutes(childrenRef); } }); result.forEach(route => { delete route.children; }); return result; } ================================================ FILE: src/utils/validate-path.util.ts ================================================ export const validatePath = (path: string): string => path ? path.startsWith('/') ? ('/' + path.replace(/\/+$/, '')).replace(/\/+/g, '/') : '/' + path.replace(/\/+$/, '') : '/'; ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "declaration": true, "module": "commonjs", "moduleResolution": "node", "outDir": "./lib", "preserveConstEnums": true, "rootDir": "./src", "baseUrl": ".", "experimentalDecorators": true, "emitDecoratorMetadata": true, "sourceMap": true, "target": "es6", "typeRoots": [ "node_modules/@types" ], "lib": [ "es7" ] }, "include": [ "src/**/*" ], "exclude": [ "node_modules" ] } ================================================ FILE: tsconfig.prod.json ================================================ { "extends": "./tsconfig.json", "exclude": [ "src/**/*.spec.ts" ] } ================================================ FILE: tslint.json ================================================ { "defaultSeverity": "warn", "extends": ["tslint:recommended"], "jsRules": { "no-unused-expression": true }, "rules": { "eofline": false, "quotemark": [true, "single"], "indent": false, "ordered-imports": [false], "max-line-length": [true, 100], "member-ordering": [false], "curly": false, "interface-name": [false], "array-type": [false], "no-empty-interface": false, "no-empty": false, "arrow-parens": false, "object-literal-sort-keys": false, "no-unused-expression": false, "max-classes-per-file": [false], "ban-types": false, "variable-name": [false], "one-line": [false], "one-variable-per-declaration": [false] }, "rulesDirectory": [] } ================================================ FILE: wallaby.js ================================================ module.exports = function() { return { files: ['src/**/*.ts', 'jest.json', '!src/**/*.spec.ts'], // <-- tests: ['src/**/*.spec.ts'], env: { type: 'node', runner: 'node' }, testFramework: 'jest', setup: function(wallaby) { var jestConfig = require('./jest.json').jest; wallaby.testFramework.configure(jestConfig); }, }; };