├── .github ├── dependabot.yml ├── semantic.yml └── workflows │ ├── auto-approve-dependabot-workflow.yml │ ├── continuous-deployment-workflow.yml │ ├── continuous-integration-workflow.yml │ └── lock-closed-issues-workflow.yml ├── .gitignore ├── .prettierrc.yml ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── codecov.yml ├── docs └── lang │ └── chinese │ └── README.md ├── eslint.config.mjs ├── jest.config.js ├── jest.setup.js ├── lang └── chinese │ └── README.md ├── package-lock.json ├── package.json ├── sample ├── sample1-simple-controller │ ├── UserController.ts │ └── app.ts ├── sample11-complete-sample-express │ ├── app.ts │ └── modules │ │ ├── blog │ │ ├── controllers │ │ │ └── BlogController.ts │ │ └── middlewares │ │ │ ├── BlogErrorHandler.ts │ │ │ └── BlogMiddleware.ts │ │ ├── post │ │ ├── controllers │ │ │ └── PostController.ts │ │ └── middlewares │ │ │ ├── PostErrorHandler.ts │ │ │ └── PostMiddleware.ts │ │ └── question │ │ ├── controllers │ │ └── QuestionController.ts │ │ └── middlewares │ │ ├── QuestionErrorHandler.ts │ │ └── QuestionMiddleware.ts ├── sample12-session-support │ ├── UserController.ts │ └── app.ts ├── sample13-koa-views-render │ ├── BlogController.ts │ ├── app.ts │ └── blog.html ├── sample14-custom-decorator │ ├── QuestionController.ts │ ├── User.ts │ ├── UserFromSession.ts │ └── app.ts ├── sample15-authorized │ ├── QuestionController.ts │ └── app.ts ├── sample16-current-user │ ├── QuestionController.ts │ ├── User.ts │ └── app.ts ├── sample17-controllers-inheritance │ ├── app.ts │ ├── controllers │ │ ├── AbstractContollerTemplate.ts │ │ ├── ArticleController.ts │ │ ├── CategoryController.ts │ │ └── ProductController.ts │ ├── interface │ │ ├── IInstance.ts │ │ └── IPayload.ts │ └── repository │ │ └── MockedRepository.ts ├── sample2-controllers-from-directory │ ├── app.ts │ └── controllers │ │ ├── BlogController.ts │ │ └── PostController.ts ├── sample3-promise-support │ ├── BlogController.ts │ └── app.ts ├── sample4-extra-parameters │ ├── BlogController.ts │ └── app.ts ├── sample5-http-errors │ ├── BlogController.ts │ └── app.ts ├── sample6-global-middlewares │ ├── AllErrorsHandler.ts │ ├── BlogController.ts │ ├── CompressionMiddleware.ts │ ├── EndTimerMiddleware.ts │ ├── LoggerMiddleware.ts │ ├── StartTimerMiddleware.ts │ └── app.ts ├── sample7-parsed-models │ ├── Photo.ts │ ├── User.ts │ ├── UserController.ts │ ├── UserFilter.ts │ └── app.ts └── sample9-use-and-middlewares │ ├── AllControllerActionsMiddleware.ts │ ├── BlogController.ts │ ├── CompressionMiddleware.ts │ └── app.ts ├── src ├── Action.ts ├── ActionParameterHandler.ts ├── AuthorizationChecker.ts ├── CurrentUserChecker.ts ├── CustomParameterDecorator.ts ├── InterceptorInterface.ts ├── RoleChecker.ts ├── RoutingControllers.ts ├── RoutingControllersOptions.ts ├── container.ts ├── decorator-options │ ├── BodyOptions.ts │ ├── ControllerOptions.ts │ ├── HandlerOptions.ts │ ├── ParamOptions.ts │ └── UploadOptions.ts ├── decorator │ ├── All.ts │ ├── Authorized.ts │ ├── Body.ts │ ├── BodyParam.ts │ ├── ContentType.ts │ ├── Controller.ts │ ├── CookieParam.ts │ ├── CookieParams.ts │ ├── Ctx.ts │ ├── CurrentUser.ts │ ├── Delete.ts │ ├── Get.ts │ ├── Head.ts │ ├── Header.ts │ ├── HeaderParam.ts │ ├── HeaderParams.ts │ ├── HttpCode.ts │ ├── Interceptor.ts │ ├── JsonController.ts │ ├── Location.ts │ ├── Method.ts │ ├── Middleware.ts │ ├── OnNull.ts │ ├── OnUndefined.ts │ ├── Param.ts │ ├── Params.ts │ ├── Patch.ts │ ├── Post.ts │ ├── Put.ts │ ├── QueryParam.ts │ ├── QueryParams.ts │ ├── Redirect.ts │ ├── Render.ts │ ├── Req.ts │ ├── Res.ts │ ├── ResponseClassTransformOptions.ts │ ├── Session.ts │ ├── SessionParam.ts │ ├── State.ts │ ├── UploadedFile.ts │ ├── UploadedFiles.ts │ ├── UseAfter.ts │ ├── UseBefore.ts │ └── UseInterceptor.ts ├── driver │ ├── BaseDriver.ts │ ├── express │ │ ├── ExpressDriver.ts │ │ ├── ExpressErrorMiddlewareInterface.ts │ │ └── ExpressMiddlewareInterface.ts │ └── koa │ │ ├── KoaDriver.ts │ │ └── KoaMiddlewareInterface.ts ├── error │ ├── AccessDeniedError.ts │ ├── AuthorizationCheckerNotDefinedError.ts │ ├── AuthorizationRequiredError.ts │ ├── CurrentUserCheckerNotDefinedError.ts │ ├── ParamNormalizationError.ts │ ├── ParamRequiredError.ts │ └── ParameterParseJsonError.ts ├── http-error │ ├── BadRequestError.ts │ ├── ForbiddenError.ts │ ├── HttpError.ts │ ├── InternalServerError.ts │ ├── MethodNotAllowedError.ts │ ├── NotAcceptableError.ts │ ├── NotFoundError.ts │ ├── UnauthorizedError.ts │ └── UnprocessableEntityError.ts ├── index.ts ├── metadata-builder │ ├── MetadataArgsStorage.ts │ └── MetadataBuilder.ts ├── metadata │ ├── ActionMetadata.ts │ ├── ControllerMetadata.ts │ ├── InterceptorMetadata.ts │ ├── MiddlewareMetadata.ts │ ├── ParamMetadata.ts │ ├── ResponseHandleMetadata.ts │ ├── UseMetadata.ts │ ├── args │ │ ├── ActionMetadataArgs.ts │ │ ├── ControllerMetadataArgs.ts │ │ ├── ErrorHandlerMetadataArgs.ts │ │ ├── InterceptorMetadataArgs.ts │ │ ├── MiddlewareMetadataArgs.ts │ │ ├── ParamMetadataArgs.ts │ │ ├── ResponseHandleMetadataArgs.ts │ │ ├── UseInterceptorMetadataArgs.ts │ │ └── UseMetadataArgs.ts │ └── types │ │ ├── ActionType.ts │ │ ├── ParamType.ts │ │ └── ResponseHandlerType.ts └── util │ ├── container.ts │ ├── importClassesFromDirectories.ts │ ├── isPromiseLike.ts │ └── runInSequence.ts ├── test ├── ActionParameterHandler.spec.ts ├── fakes │ └── global-options │ │ ├── FakeService.ts │ │ ├── SessionMiddleware.ts │ │ ├── User.ts │ │ ├── express-middlewares │ │ ├── post │ │ │ └── PostMiddleware.ts │ │ └── question │ │ │ ├── QuestionErrorHandler.ts │ │ │ └── QuestionMiddleware.ts │ │ ├── first-controllers │ │ ├── post │ │ │ └── PostController.ts │ │ └── question │ │ │ ├── AnswerController.ts │ │ │ └── QuestionController.ts │ │ ├── koa-middlewares │ │ ├── FileMiddleware.ts │ │ ├── SetStateMiddleware.ts │ │ └── VideoMiddleware.ts │ │ └── second-controllers │ │ ├── PhotoController.ts │ │ └── VideoController.ts ├── functional │ ├── action-options.spec.ts │ ├── action-params.spec.ts │ ├── auth-decorator.spec.ts │ ├── class-transformer-options.spec.ts │ ├── class-validator-options.spec.ts │ ├── container.spec.ts │ ├── controller-base-routes.spec.ts │ ├── controller-methods.spec.ts │ ├── controller-options.spec.ts │ ├── defaults.spec.ts │ ├── error-subclasses.spec.ts │ ├── express-custom-error-handling.spec.ts │ ├── express-error-handling.spec.ts │ ├── express-global-before-error-handling.spec.ts │ ├── express-middlewares.spec.ts │ ├── express-render-decorator.spec.ts │ ├── global-options.spec.ts │ ├── interceptors.spec.ts │ ├── json-controller-methods.spec.ts │ ├── koa-render-decorator.spec.ts │ ├── koa-trailing-slash.spec.ts │ ├── load-from-directory.spec.ts │ ├── middlewares-order.spec.ts │ ├── other-controller-decorators.spec.ts │ ├── redirect-decorator.spec.ts │ └── special-result-send.spec.ts ├── resources │ ├── ejs-render-test-locals-spec.html │ ├── ejs-render-test-spec.html │ ├── render-test-locals-spec.html │ ├── render-test-spec.html │ └── sample-text-file.txt ├── unit │ └── controller-inheritance.spec.ts └── utilities │ └── axios.ts ├── tsconfig.json ├── tsconfig.prod.cjs.json ├── tsconfig.prod.esm2015.json ├── tsconfig.prod.json ├── tsconfig.prod.types.json └── tsconfig.spec.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | timezone: Europe/Budapest 9 | open-pull-requests-limit: 5 10 | versioning-strategy: increase 11 | commit-message: 12 | prefix: build 13 | include: scope 14 | -------------------------------------------------------------------------------- /.github/semantic.yml: -------------------------------------------------------------------------------- 1 | titleAndCommits: true 2 | allowMergeCommits: false 3 | scopes: 4 | - deps 5 | - deps-dev 6 | types: 7 | - feat 8 | - fix 9 | - docs 10 | - style 11 | - refactor 12 | - perf 13 | - test 14 | - build 15 | - ci 16 | - chore 17 | - revert 18 | - merge 19 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve-dependabot-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: 3 | pull_request_target 4 | jobs: 5 | dependabot: 6 | runs-on: ubuntu-latest 7 | if: github.actor == 'dependabot[bot]' 8 | steps: 9 | - name: 'Auto approve PR by Dependabot' 10 | uses: hmarr/auto-approve-action@v2.0.0 11 | with: 12 | github-token: "${{ secrets.TYPESTACK_BOT_TOKEN }}" 13 | - name: 'Comment merge command' 14 | uses: actions/github-script@v3 15 | with: 16 | github-token: ${{secrets.TYPESTACK_BOT_TOKEN }} 17 | script: | 18 | await github.issues.createComment({ 19 | owner: context.repo.owner, 20 | repo: context.repo.repo, 21 | issue_number: context.issue.number, 22 | body: '@dependabot squash and merge' 23 | }) 24 | -------------------------------------------------------------------------------- /.github/workflows/continuous-deployment-workflow.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | publish: 7 | name: Publish to NPM 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: 'lts/*' 14 | registry-url: https://registry.npmjs.org 15 | - run: npm ci --ignore-scripts 16 | - run: npm run prettier:check 17 | - run: npm run lint:check 18 | - run: npm run test:ci 19 | - run: npm run build:es2015 20 | - run: npm run build:cjs 21 | - run: npm run build:types 22 | - run: cp LICENSE build/LICENSE 23 | - run: cp README.md build/README.md 24 | - run: jq 'del(.devDependencies) | del(.scripts)' package.json > build/package.json 25 | - run: npm publish ./build 26 | env: 27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/continuous-integration-workflow.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | jobs: 4 | checks: 5 | name: Linters 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | - uses: actions/setup-node@v3 10 | with: 11 | node-version: 'lts/*' 12 | - run: npm ci --ignore-scripts 13 | - run: npm run prettier:check 14 | - run: npm run lint:check 15 | tests: 16 | name: Tests 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | node-version: ['lts/*', 'current'] 21 | fail-fast: false 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Setting up Node.js (v${{ matrix.node-version }}.x) 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: npm ci --ignore-scripts 29 | - run: npm run test:ci 30 | - name: Upload coverage to Codecov 31 | uses: codecov/codecov-action@v3 32 | if: ${{ matrix.node-version == 'current' }} 33 | with: 34 | file: ./coverage/clover.xml 35 | token: ${{ secrets.CODECOV_TOKEN }} 36 | commit: ${{ github.sha }} 37 | branch: ${{ github.ref }} 38 | build: 39 | name: Build 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v3 43 | - uses: actions/setup-node@v3 44 | with: 45 | node-version: 'lts/*' 46 | - run: npm ci --ignore-scripts 47 | - run: npm run build:es2015 48 | - run: npm run build:cjs 49 | - run: npm run build:types -------------------------------------------------------------------------------- /.github/workflows/lock-closed-issues-workflow.yml: -------------------------------------------------------------------------------- 1 | name: 'Lock inactive threads' 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | jobs: 6 | lock: 7 | name: Lock closed issues 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: dessant/lock-threads@v2 11 | with: 12 | github-token: ${{ github.token }} 13 | issue-lock-inactive-days: 30 14 | pr-lock-inactive-days: 30 15 | issue-lock-comment: > 16 | This issue has been automatically locked since there 17 | has not been any recent activity after it was closed. 18 | Please open a new issue for related bugs. 19 | pr-lock-comment: > 20 | This pull request has been automatically locked since there 21 | has not been any recent activity after it was closed. 22 | Please open a new issue for related bugs. 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Log files 2 | logs 3 | *.log 4 | *.tmp 5 | *.tmp.* 6 | log.txt 7 | npm-debug.log* 8 | 9 | # Testing output 10 | lib-cov/** 11 | coverage/** 12 | 13 | # Environment files 14 | .env 15 | 16 | # Dependency directories 17 | node_modules 18 | 19 | # MacOS related files 20 | *.DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | ._* 24 | UserInterfaceState.xcuserstate 25 | 26 | # Windows related files 27 | Thumbs.db 28 | Desktop.ini 29 | $RECYCLE.BIN/ 30 | 31 | # IDE - Sublime 32 | *.sublime-project 33 | *.sublime-workspace 34 | 35 | # IDE - VSCode 36 | .vscode/** 37 | !.vscode/tasks.json 38 | !.vscode/launch.json 39 | 40 | # IDE - IntelliJ 41 | .idea 42 | 43 | # Compilation output folders 44 | dist/ 45 | build/ 46 | tmp/ 47 | out-tsc/ 48 | temp 49 | 50 | # Files for playing around locally 51 | playground.ts 52 | playground.js 53 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | printWidth: 120 2 | tabWidth: 2 3 | useTabs: false 4 | semi: true 5 | singleQuote: true 6 | trailingComma: es5 7 | bracketSpacing: true 8 | arrowParens: avoid 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "address": "0.0.0.0", 9 | "type": "node", 10 | "request": "attach", 11 | "name": "Routing Controller", 12 | "restart": true, 13 | "port": 20001, 14 | "remoteRoot": "/home/nodebrick/application", 15 | "localRoot": "${workspaceFolder}", 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015-2020 TypeStack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 70..100 3 | round: down 4 | precision: 2 5 | status: 6 | default: 7 | threshold: 3 8 | comment: off 9 | ignore: 10 | - testing/**/*.ts 11 | - src/**/*.interface.ts -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 2 | import tsParser from "@typescript-eslint/parser"; 3 | import path from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | import js from "@eslint/js"; 6 | import { FlatCompat } from "@eslint/eslintrc"; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename); 10 | const compat = new FlatCompat({ 11 | baseDirectory: __dirname, 12 | recommendedConfig: js.configs.recommended, 13 | allConfig: js.configs.all 14 | }); 15 | 16 | export default [...compat.extends( 17 | "plugin:@typescript-eslint/recommended", 18 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 19 | "plugin:jest/recommended", 20 | "prettier", 21 | ), { 22 | plugins: { 23 | "@typescript-eslint": typescriptEslint, 24 | }, 25 | 26 | languageOptions: { 27 | parser: tsParser, 28 | ecmaVersion: 2018, 29 | sourceType: "module", 30 | 31 | parserOptions: { 32 | project: ["./tsconfig.json", "./tsconfig.spec.json"], 33 | }, 34 | }, 35 | 36 | rules: { 37 | "@typescript-eslint/explicit-member-accessibility": "off", 38 | "@typescript-eslint/no-angle-bracket-type-assertion": "off", 39 | "@typescript-eslint/no-parameter-properties": "off", 40 | "@typescript-eslint/explicit-function-return-type": "off", 41 | "@typescript-eslint/member-delimiter-style": "off", 42 | "@typescript-eslint/no-inferrable-types": "off", 43 | "@typescript-eslint/no-explicit-any": "off", 44 | "@typescript-eslint/member-ordering": "error", 45 | "@typescript-eslint/no-unused-vars": ["error", { 46 | args: "none", 47 | }], 48 | "@typescript-eslint/ban-types": "off", 49 | "@typescript-eslint/no-unsafe-return": "off", 50 | "@typescript-eslint/no-unsafe-assignment": "off", 51 | "@typescript-eslint/no-unsafe-call": "off", 52 | "@typescript-eslint/no-unsafe-member-access": "off", 53 | "@typescript-eslint/explicit-module-boundary-types": "off", 54 | "@typescript-eslint/no-unsafe-argument": "off", 55 | "@typescript-eslint/no-var-requires": "off", 56 | "@typescript-eslint/no-unsafe-function-type": "off", 57 | "@typescript-eslint/no-wrapper-object-types": "off", 58 | "@typescript-eslint/no-require-imports": "off", 59 | "@typescript-eslint/no-redundant-type-constituents": "off", 60 | }, 61 | }]; -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | collectCoverageFrom: ['src/**/*.ts', '!src/**/index.ts', '!src/**/*.interface.ts'], 5 | globals: {}, 6 | setupFilesAfterEnv: ["./jest.setup.js"], 7 | transform: { 8 | '^.+\\.tsx?$': [ 9 | 'ts-jest', 10 | {tsconfig: './tsconfig.spec.json'}, 11 | ], 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(30000); 2 | 3 | require("reflect-metadata"); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "routing-controllers", 3 | "private": false, 4 | "version": "0.11.2", 5 | "description": "Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage for Express / Koa using TypeScript.", 6 | "author": "TypeStack contributors", 7 | "license": "MIT", 8 | "sideEffects": false, 9 | "main": "./cjs/index.js", 10 | "module": "./esm2015/index.js", 11 | "es2015": "./esm2015/index.js", 12 | "typings": "./types/index.d.ts", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/typestack/routing-controllers.git" 16 | }, 17 | "tags": [ 18 | "express", 19 | "express.js", 20 | "typescript", 21 | "typescript-express", 22 | "typescript-express.js", 23 | "express-controllers", 24 | "controllers" 25 | ], 26 | "scripts": { 27 | "build": "npm run build:cjs", 28 | "build:clean": "rimraf build", 29 | "build:es2015": "tsc --project tsconfig.prod.esm2015.json", 30 | "build:cjs": "tsc --project tsconfig.prod.cjs.json", 31 | "build:types": "tsc --project tsconfig.prod.types.json", 32 | "prettier:fix": "prettier --write \"**/*.{ts,md}\"", 33 | "prettier:check": "prettier --check \"**/*.{ts,md}\"", 34 | "lint:fix": "eslint --max-warnings 0 --fix src/**/*.ts", 35 | "lint:check": "eslint --max-warnings 0 src/**/*.ts", 36 | "test": "jest --verbose --runInBand", 37 | "test:watch": "jest --watch", 38 | "test:ci": "jest --runInBand --no-cache --coverage --verbose", 39 | "test:debug": "NODE_ENV= node --inspect=0.0.0.0:20001 ./node_modules/.bin/jest --colors --runInBand --no-coverage --watch" 40 | }, 41 | "dependencies": { 42 | "cookie": "^1.0.2", 43 | "glob": "^11.0.0", 44 | "reflect-metadata": "^0.2.2", 45 | "template-url": "^1.0.0" 46 | }, 47 | "peerDependencies": { 48 | "class-transformer": "^0.5.1", 49 | "class-validator": "^0.14.1" 50 | }, 51 | "devDependencies": { 52 | "@eslint/eslintrc": "^3.1.0", 53 | "@eslint/js": "^9.21.0", 54 | "@koa/cors": "^5.0.0", 55 | "@koa/ejs": "^5.1.0", 56 | "@types/express": "^5.0.0", 57 | "@types/express-session": "^1.18.1", 58 | "@types/jest": "^29.5.14", 59 | "@types/koa": "^2.15.0", 60 | "@types/koa__ejs": "^5.1.0", 61 | "@types/multer": "^1.4.12", 62 | "@types/node": "^16.18.3", 63 | "@types/serve-static": "^1.15.7", 64 | "@typescript-eslint/eslint-plugin": "^8.25.0", 65 | "@typescript-eslint/parser": "^8.16.0", 66 | "axios": "^1.8.2", 67 | "body-parser": "^1.20.3", 68 | "chakram": "^1.5.0", 69 | "class-transformer": "^0.5.1", 70 | "class-validator": "^0.14.1", 71 | "cors": "^2.8.5", 72 | "eslint": "^9.10.0", 73 | "eslint-config-prettier": "^9.1.0", 74 | "eslint-plugin-jest": "^28.11.1", 75 | "form-data": "^4.0.1", 76 | "handlebars": "^4.7.8", 77 | "http-status-codes": "^2.3.0", 78 | "husky": "^9.1.7", 79 | "jest": "^29.7.0", 80 | "koa-convert": "^2.0.0", 81 | "koa-session": "^6.4.0", 82 | "lint-staged": "^15.4.3", 83 | "multer": "^2.0.0", 84 | "mustache-express": "^1.3.2", 85 | "prettier": "^3.5.0", 86 | "rimraf": "^6.0.1", 87 | "ts-jest": "^29.2.5", 88 | "ts-node": "^10.9.2", 89 | "ts-node-dev": "^2.0.0", 90 | "typedi": "^0.10.0", 91 | "typescript": "^5.7.2" 92 | }, 93 | "optionalDependencies": { 94 | "@koa/multer": "^3.0.2", 95 | "@koa/router": "^12.0.1", 96 | "body-parser": "^1.20.2", 97 | "express": "^4.21.2", 98 | "express-session": "^1.18.0", 99 | "koa": "^2.16.1", 100 | "koa-bodyparser": "^4.4.1", 101 | "multer": "^2.0.0" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /sample/sample1-simple-controller/UserController.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { Controller } from '../../src/decorator/Controller'; 3 | import { Get } from '../../src/decorator/Get'; 4 | import { Req } from '../../src/index'; 5 | import { Post } from '../../src/decorator/Post'; 6 | import { Put } from '../../src/decorator/Put'; 7 | import { Patch } from '../../src/decorator/Patch'; 8 | import { Delete } from '../../src/decorator/Delete'; 9 | import { ContentType } from '../../src/decorator/ContentType'; 10 | 11 | @Controller() 12 | export class UserController { 13 | @Get('/users') 14 | @ContentType('application/json') 15 | getAll() { 16 | return [ 17 | { id: 1, name: 'First user!' }, 18 | { id: 2, name: 'Second user!' }, 19 | ]; 20 | } 21 | 22 | @Get('/users/:id') 23 | getOne(@Req() request: Request) { 24 | return 'User #' + request.params.id; 25 | } 26 | 27 | @Post('/users') 28 | post(@Req() request: Request) { 29 | let user = JSON.stringify(request.body); // probably you want to install body-parser for express 30 | return 'User ' + user + ' !saved!'; 31 | } 32 | 33 | @Put('/users/:id') 34 | put(@Req() request: Request) { 35 | return 'User #' + request.params.id + ' has been putted!'; 36 | } 37 | 38 | @Patch('/users/:id') 39 | patch(@Req() request: Request) { 40 | return 'User #' + request.params.id + ' has been patched!'; 41 | } 42 | 43 | @Delete('/users/:id') 44 | remove(@Req() request: Request) { 45 | return 'User #' + request.params.id + ' has been removed!'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sample/sample1-simple-controller/app.ts: -------------------------------------------------------------------------------- 1 | import { createExpressServer } from '../../src/index'; 2 | 3 | require('./UserController'); 4 | 5 | const app = createExpressServer(); // register controllers routes in our express application 6 | app.listen(3001); // run express app 7 | 8 | console.log('Express server is running on port 3001. Open http://localhost:3001/users/'); 9 | -------------------------------------------------------------------------------- /sample/sample11-complete-sample-express/app.ts: -------------------------------------------------------------------------------- 1 | import { createExpressServer } from '../../src/index'; 2 | 3 | // base directory. we use it because file in "required" in another module 4 | const baseDir = __dirname; 5 | 6 | // express is used just as an example here. you can also use koa 7 | // to do it simply use createKoaServer instead of createExpressServer 8 | const app = createExpressServer({ 9 | controllers: [baseDir + '/modules/**/controllers/*{.js,.ts}'], 10 | middlewares: [baseDir + '/modules/**/middlewares/*{.js,.ts}'], 11 | }); 12 | app.listen(3001); 13 | 14 | console.log('Express server is running on port 3001. Open http://localhost:3001/blogs/'); 15 | -------------------------------------------------------------------------------- /sample/sample11-complete-sample-express/modules/blog/controllers/BlogController.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { JsonController } from '../../../../../src/decorator/JsonController'; 3 | import { Get } from '../../../../../src/decorator/Get'; 4 | import { Post } from '../../../../../src/decorator/Post'; 5 | import { Req } from '../../../../../src/decorator/Req'; 6 | import { Put } from '../../../../../src/decorator/Put'; 7 | import { Patch } from '../../../../../src/decorator/Patch'; 8 | import { Delete } from '../../../../../src/decorator/Delete'; 9 | 10 | @JsonController() 11 | export class BlogController { 12 | @Get('/blogs') 13 | getAll() { 14 | console.log('Getting blogs...'); 15 | return this.createPromise( 16 | [ 17 | { id: 1, name: 'Blog 1!' }, 18 | { id: 2, name: 'Blog 2!' }, 19 | ], 20 | 3000 21 | ); 22 | } 23 | 24 | @Get('/blogs/:id') 25 | getOne() { 26 | return this.createPromise({ id: 1, name: 'Blog 1!' }, 3000); 27 | } 28 | 29 | @Post('/blogs') 30 | post(@Req() request: Request) { 31 | let blog = JSON.stringify(request.body); 32 | return this.createPromise('Blog ' + blog + ' !saved!', 3000); 33 | } 34 | 35 | @Put('/blogs/:id') 36 | put(@Req() request: Request) { 37 | return this.createPromise('Blog #' + request.params.id + ' has been putted!', 3000); 38 | } 39 | 40 | @Patch('/blogs/:id') 41 | patch(@Req() request: Request) { 42 | return this.createPromise('Blog #' + request.params.id + ' has been patched!', 3000); 43 | } 44 | 45 | @Delete('/blogs/:id') 46 | remove(@Req() request: Request) { 47 | return this.createPromise('Blog #' + request.params.id + ' has been removed!', 3000); 48 | } 49 | 50 | private createPromise(data: any, timeout: number): Promise { 51 | return new Promise((ok, fail) => { 52 | setTimeout(() => ok(data), timeout); 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sample/sample11-complete-sample-express/modules/blog/middlewares/BlogErrorHandler.ts: -------------------------------------------------------------------------------- 1 | import { ExpressErrorMiddlewareInterface } from '../../../../../src/driver/express/ExpressErrorMiddlewareInterface'; 2 | import { Middleware } from '../../../../../src/decorator/Middleware'; 3 | 4 | @Middleware({ type: 'after' }) 5 | export class BlogErrorHandler implements ExpressErrorMiddlewareInterface { 6 | error(error: any, request: any, response: any, next?: Function): void { 7 | console.log('Error handled on blog handler: ', error); 8 | next(error); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/sample11-complete-sample-express/modules/blog/middlewares/BlogMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../../../../src/driver/express/ExpressMiddlewareInterface'; 2 | 3 | export class BlogMiddleware implements ExpressMiddlewareInterface { 4 | use(request: any, response: any, next?: Function): any { 5 | console.log('logging request from blog middleware...'); 6 | next('ERROR IN BLOG MIDDLEWARE'); 7 | // console.log("extra logging request from blog middleware..."); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample/sample11-complete-sample-express/modules/post/controllers/PostController.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { JsonController } from '../../../../../src/decorator/JsonController'; 3 | import { Get } from '../../../../../src/decorator/Get'; 4 | import { Post } from '../../../../../src/decorator/Post'; 5 | import { Put } from '../../../../../src/decorator/Put'; 6 | import { Req } from '../../../../../src/decorator/Req'; 7 | import { Patch } from '../../../../../src/decorator/Patch'; 8 | import { Delete } from '../../../../../src/decorator/Delete'; 9 | 10 | @JsonController() 11 | export class PostController { 12 | @Get('/posts') 13 | getAll() { 14 | return this.createPromise( 15 | [ 16 | { id: 1, name: 'Post 1!' }, 17 | { id: 2, name: 'Post 2!' }, 18 | ], 19 | 3000 20 | ); 21 | } 22 | 23 | @Get('/posts/:id') 24 | getOne() { 25 | return this.createPromise({ id: 1, name: 'Post 1!' }, 3000); 26 | } 27 | 28 | @Post('/posts') 29 | post(@Req() request: Request) { 30 | let post = JSON.stringify(request.body); 31 | return this.createPromise('Post ' + post + ' !saved!', 3000); 32 | } 33 | 34 | @Put('/posts/:id') 35 | put(@Req() request: Request) { 36 | return this.createPromise('Post #' + request.params.id + ' has been putted!', 3000); 37 | } 38 | 39 | @Patch('/posts/:id') 40 | patch(@Req() request: Request) { 41 | return this.createPromise('Post #' + request.params.id + ' has been patched!', 3000); 42 | } 43 | 44 | @Delete('/posts/:id') 45 | remove(@Req() request: Request) { 46 | return this.createPromise('Post #' + request.params.id + ' has been removed!', 3000); 47 | } 48 | 49 | private createPromise(data: any, timeout: number): Promise { 50 | return new Promise((ok, fail) => { 51 | setTimeout(() => ok(data), timeout); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /sample/sample11-complete-sample-express/modules/post/middlewares/PostErrorHandler.ts: -------------------------------------------------------------------------------- 1 | import { ExpressErrorMiddlewareInterface } from '../../../../../src/driver/express/ExpressErrorMiddlewareInterface'; 2 | import { Middleware } from '../../../../../src/decorator/Middleware'; 3 | 4 | @Middleware({ type: 'after' }) 5 | export class PostErrorHandler implements ExpressErrorMiddlewareInterface { 6 | error(error: any, request: any, response: any, next?: Function): void { 7 | console.log('Error handled on post handler: ', error); 8 | next(error); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/sample11-complete-sample-express/modules/post/middlewares/PostMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../../../../src/driver/express/ExpressMiddlewareInterface'; 2 | 3 | export class PostMiddleware implements ExpressMiddlewareInterface { 4 | use(request: any, response: any, next?: Function): any { 5 | console.log('logging request from post middleware...'); 6 | next(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample/sample11-complete-sample-express/modules/question/controllers/QuestionController.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { JsonController } from '../../../../../src/decorator/JsonController'; 3 | import { Get } from '../../../../../src/decorator/Get'; 4 | import { Param } from '../../../../../src/decorator/Param'; 5 | import { Post } from '../../../../../src/decorator/Post'; 6 | import { Req } from '../../../../../src/decorator/Req'; 7 | import { Put } from '../../../../../src/decorator/Put'; 8 | import { Patch } from '../../../../../src/decorator/Patch'; 9 | import { Delete } from '../../../../../src/decorator/Delete'; 10 | 11 | @JsonController() 12 | export class QuestionController { 13 | @Get('/questions') 14 | getAll() { 15 | return this.createPromise( 16 | [ 17 | { id: 1, name: 'Question 1!' }, 18 | { id: 2, name: 'Question 2!' }, 19 | ], 20 | 3000 21 | ); 22 | } 23 | 24 | @Get('/questions/:id') 25 | getOne(@Param('id') id: number) { 26 | if (!id) return Promise.reject(new Error('No id is specified')); 27 | 28 | return this.createPromise({ id: 1, name: 'Question 1!' }, 3000); 29 | } 30 | 31 | @Post('/questions') 32 | post(@Req() request: Request) { 33 | let question = JSON.stringify(request.body); 34 | return this.createPromise('Question ' + question + ' !saved!', 3000); 35 | } 36 | 37 | @Put('/questions/:id') 38 | put(@Req() request: Request) { 39 | return this.createPromise('Question #' + request.params.id + ' has been putted!', 3000); 40 | } 41 | 42 | @Patch('/questions/:id') 43 | patch(@Req() request: Request) { 44 | return this.createPromise('Question #' + request.params.id + ' has been patched!', 3000); 45 | } 46 | 47 | @Delete('/questions/:id') 48 | remove(@Req() request: Request) { 49 | return this.createPromise('Question #' + request.params.id + ' has been removed!', 3000); 50 | } 51 | 52 | private createPromise(data: any, timeout: number): Promise { 53 | return new Promise((ok, fail) => { 54 | setTimeout(() => ok(data), timeout); 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /sample/sample11-complete-sample-express/modules/question/middlewares/QuestionErrorHandler.ts: -------------------------------------------------------------------------------- 1 | import { ExpressErrorMiddlewareInterface } from '../../../../../src/driver/express/ExpressErrorMiddlewareInterface'; 2 | import { Middleware } from '../../../../../src/decorator/Middleware'; 3 | 4 | @Middleware({ type: 'after' }) 5 | export class QuestionErrorHandler implements ExpressErrorMiddlewareInterface { 6 | error(error: any, request: any, response: any, next?: Function): void { 7 | console.log('Error handled on question handler: ', error); 8 | next(error); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/sample11-complete-sample-express/modules/question/middlewares/QuestionMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../../../../src/driver/express/ExpressMiddlewareInterface'; 2 | 3 | export class QuestionMiddleware implements ExpressMiddlewareInterface { 4 | use(request: any, response: any, next?: Function): any { 5 | console.log('logging request from question middleware...'); 6 | next(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample/sample12-session-support/UserController.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { Controller } from '../../src/decorator/Controller'; 3 | import { Get } from '../../src/decorator/Get'; 4 | import { Req } from '../../src/index'; 5 | import { Post } from '../../src/decorator/Post'; 6 | import { Put } from '../../src/decorator/Put'; 7 | import { Patch } from '../../src/decorator/Patch'; 8 | import { Delete } from '../../src/decorator/Delete'; 9 | import { Param } from '../../src/decorator/Param'; 10 | import { Session } from '../../src/decorator/Session'; 11 | import { SessionParam } from '../../src/decorator/SessionParam'; 12 | import { ContentType } from '../../src/decorator/ContentType'; 13 | 14 | @Controller() 15 | export class UserController { 16 | @Get('/users') 17 | @ContentType('application/json') 18 | getAll() { 19 | return [ 20 | { id: 1, name: 'First user!' }, 21 | { id: 2, name: 'Second user!' }, 22 | ]; 23 | } 24 | 25 | @Get('/users/:id') 26 | @ContentType('application/json') 27 | getOne(@SessionParam('user') user: any) { 28 | return user; 29 | } 30 | 31 | @Post('/users') 32 | post(@Req() request: Request) { 33 | let user = JSON.stringify(request.body); // probably you want to install body-parser for express 34 | return 'User ' + user + ' !saved!'; 35 | } 36 | 37 | @Put('/users/:id') 38 | put(@Param('id') id: number, @Session() session: Express.Session) { 39 | (session as any).user = { name: 'test', number: id }; 40 | return 'User has been putted!'; 41 | } 42 | 43 | @Patch('/users/:id') 44 | patch(@Req() request: Request) { 45 | return 'User #' + request.params.id + ' has been patched!'; 46 | } 47 | 48 | @Delete('/users/:id') 49 | remove(@Req() request: Request) { 50 | return 'User #' + request.params.id + ' has been removed!'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /sample/sample12-session-support/app.ts: -------------------------------------------------------------------------------- 1 | import { useExpressServer } from '../../src/index'; 2 | import * as express from 'express'; 3 | import * as session from 'express-session'; 4 | 5 | require('./UserController'); 6 | 7 | const app = express(); 8 | app.use(session()); // use session middleware 9 | 10 | useExpressServer(app); // register controllers routes in our express application 11 | app.listen(3001); // run express app 12 | 13 | console.log('Express server is running on port 3001. Open http://localhost:3001/users/'); 14 | -------------------------------------------------------------------------------- /sample/sample13-koa-views-render/BlogController.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '../../src/decorator/Controller'; 2 | import { Get } from '../../src/decorator/Get'; 3 | import { Render } from '../../src/decorator/Render'; 4 | 5 | @Controller() 6 | export class UserController { 7 | @Get('/') 8 | @Render('blog.html') 9 | blog() { 10 | return { 11 | title: 'My Blog', 12 | posts: [ 13 | { 14 | title: 'Welcome to my blog', 15 | content: 'This is my new blog built with Koa, routing-controllers and koa-views', 16 | }, 17 | { 18 | title: 'Hello World', 19 | content: 'Hello world from Koa and routing-controllers', 20 | }, 21 | ], 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/sample13-koa-views-render/app.ts: -------------------------------------------------------------------------------- 1 | import * as Koa from 'koa'; 2 | 3 | import { useKoaServer } from '../../src/index'; 4 | 5 | require('./BlogController'); 6 | 7 | const koa = new Koa(); 8 | const app = useKoaServer(koa); 9 | const path = __dirname + '/../../../../sample/sample13-koa-views-render'; 10 | 11 | let koaViews = require('koa-views'); 12 | app.use(koaViews(path, { map: { html: 'handlebars' } })); 13 | 14 | app.listen(3001); // run koa app 15 | 16 | console.log('Koa server is running on port 3001. Open http://localhost:3001/'); 17 | -------------------------------------------------------------------------------- /sample/sample13-koa-views-render/blog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 |

{{title}}

8 | {{#each posts}} 9 |

{{this.title}}

10 |

{{this.content}} 11 | {{/each}} 12 | 13 | -------------------------------------------------------------------------------- /sample/sample14-custom-decorator/QuestionController.ts: -------------------------------------------------------------------------------- 1 | import { Get } from '../../src/decorator/Get'; 2 | import { JsonController } from '../../src/decorator/JsonController'; 3 | import { UserFromSession } from './UserFromSession'; 4 | import { User } from './User'; 5 | 6 | @JsonController() 7 | export class QuestionController { 8 | @Get('/questions') 9 | all(@UserFromSession({ required: true }) user: User) { 10 | return [ 11 | { 12 | id: 1, 13 | title: 'Question created by ' + user.firstName, 14 | }, 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/sample14-custom-decorator/User.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | id: number; 3 | firstName: string; 4 | lastName: string; 5 | 6 | constructor(id: number, firstName: string, lastName: string) { 7 | this.id = id; 8 | this.firstName = firstName; 9 | this.lastName = lastName; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/sample14-custom-decorator/UserFromSession.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator } from '../../src/index'; 2 | import { User } from './User'; 3 | 4 | /** 5 | * Simple decorator - re-implementation of CurrentUser decorator. 6 | */ 7 | export function UserFromSession(options?: { required?: boolean }) { 8 | return createParamDecorator({ 9 | required: options && options.required ? true : false, 10 | value: action => { 11 | // perform queries based on token from request headers 12 | // const token = action.request.headers["authorization"]; 13 | // return database.findUserByToken(token); 14 | return new User(1, 'Johny', 'Cage'); 15 | }, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /sample/sample14-custom-decorator/app.ts: -------------------------------------------------------------------------------- 1 | import { createExpressServer } from '../../src/index'; 2 | import { QuestionController } from './QuestionController'; 3 | 4 | createExpressServer({ 5 | controllers: [QuestionController], 6 | }).listen(3001); 7 | 8 | console.log('Express server is running on port 3001. Open http://localhost:3001/questions/'); 9 | -------------------------------------------------------------------------------- /sample/sample15-authorized/QuestionController.ts: -------------------------------------------------------------------------------- 1 | import { Get } from '../../src/decorator/Get'; 2 | import { JsonController } from '../../src/decorator/JsonController'; 3 | import { Authorized } from '../../src/decorator/Authorized'; 4 | 5 | @JsonController() 6 | export class QuestionController { 7 | @Authorized() 8 | @Get('/questions') 9 | all() { 10 | return [ 11 | { 12 | id: 1, 13 | title: 'Question #1', 14 | }, 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/sample15-authorized/app.ts: -------------------------------------------------------------------------------- 1 | import { createExpressServer } from '../../src/index'; 2 | import { QuestionController } from './QuestionController'; 3 | import { Action } from '../../src/Action'; 4 | 5 | createExpressServer({ 6 | controllers: [QuestionController], 7 | authorizationChecker: async (action: Action, roles?: string[]) => { 8 | // perform queries based on token from request headers 9 | // const token = action.request.headers["authorization"]; 10 | // return database.findUserByToken(token).roles.in(roles); 11 | return false; 12 | }, 13 | }).listen(3001); 14 | 15 | console.log('Express server is running on port 3001. Open http://localhost:3001/questions/'); 16 | -------------------------------------------------------------------------------- /sample/sample16-current-user/QuestionController.ts: -------------------------------------------------------------------------------- 1 | import { Get } from '../../src/decorator/Get'; 2 | import { JsonController } from '../../src/decorator/JsonController'; 3 | import { User } from './User'; 4 | import { CurrentUser } from '../../src/decorator/CurrentUser'; 5 | 6 | @JsonController() 7 | export class QuestionController { 8 | @Get('/questions') 9 | all(@CurrentUser({ required: true }) user: User) { 10 | return [ 11 | { 12 | id: 1, 13 | title: 'Question by ' + user.firstName, 14 | }, 15 | ]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/sample16-current-user/User.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | id: number; 3 | firstName: string; 4 | lastName: string; 5 | 6 | constructor(id: number, firstName: string, lastName: string) { 7 | this.id = id; 8 | this.firstName = firstName; 9 | this.lastName = lastName; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/sample16-current-user/app.ts: -------------------------------------------------------------------------------- 1 | import { createExpressServer } from '../../src/index'; 2 | import { QuestionController } from './QuestionController'; 3 | import { Action } from '../../src/Action'; 4 | import { User } from './User'; 5 | 6 | createExpressServer({ 7 | controllers: [QuestionController], 8 | currentUserChecker: async (action: Action, value?: any) => { 9 | // perform queries based on token from request headers 10 | // const token = action.request.headers["authorization"]; 11 | // return database.findUserByToken(token); 12 | return new User(1, 'Johny', 'Cage'); 13 | }, 14 | }).listen(3001); 15 | 16 | console.log('Express server is running on port 3001. Open http://localhost:3001/questions/'); 17 | -------------------------------------------------------------------------------- /sample/sample17-controllers-inheritance/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import { useExpressServer } from '../../src/index'; 3 | 4 | let app = express(); // create express server 5 | useExpressServer(app, { 6 | controllers: [__dirname + '/controllers/*{.js,.ts}'], // register controllers routes in our express app 7 | }); 8 | app.listen(3001); // run express app 9 | 10 | console.log( 11 | 'Possible GET endpoints you may see from a browser', 12 | 'http://localhost:3001/article', 13 | 'http://localhost:3001/article/1000', 14 | 'http://localhost:3001/product', 15 | 'http://localhost:3001/product/1000', 16 | 'http://localhost:3001/category', 17 | 'http://localhost:3001/category/1000' 18 | ); 19 | -------------------------------------------------------------------------------- /sample/sample17-controllers-inheritance/controllers/AbstractContollerTemplate.ts: -------------------------------------------------------------------------------- 1 | import { Res } from '../../../src/decorator/Res'; 2 | import { Put } from '../../../src/decorator/Put'; 3 | import { Post } from '../../../src/decorator/Post'; 4 | import { Param } from '../../../src/decorator/Param'; 5 | import { Get } from '../../../src/decorator/Get'; 6 | import { Delete } from '../../../src/decorator/Delete'; 7 | import { Body } from '../../../src/decorator/Body'; 8 | 9 | import { MockedRepository } from '../repository/MockedRepository'; 10 | import { IInstance } from '../interface/IInstance'; 11 | 12 | /** 13 | * @description the base controller class used by derivatives 14 | */ 15 | export abstract class AbstractControllerTemplate { 16 | /** 17 | * @description domain part of a system, also called object|entity|model 18 | */ 19 | protected domain: string; 20 | protected repository: MockedRepository; 21 | 22 | @Post() 23 | public async create(@Body() payload: any, @Res() res: any): Promise<{}> { 24 | const item = await this.repository.create(payload); 25 | 26 | res.status(201); 27 | res.location(`/${this.domain}/${item.id}`); 28 | 29 | return {}; 30 | } 31 | 32 | @Put('/:id') 33 | public async updated(@Param('id') id: number, @Body() payload: any, @Res() res: any): Promise<{}> { 34 | await this.repository.update(id, payload); 35 | res.status(204); 36 | 37 | return {}; 38 | } 39 | 40 | @Get('/:id') 41 | public read(@Param('id') id: number, @Res() res: any): Promise { 42 | return this.repository.find(id); 43 | } 44 | 45 | @Get() 46 | public readCollection(@Res() res: any): Promise { 47 | return this.repository.getCollection(); 48 | } 49 | 50 | @Delete('/:id') 51 | public async delete(@Param('id') id: number, @Res() res: any): Promise<{}> { 52 | await this.repository.delete(id); 53 | 54 | return {}; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sample/sample17-controllers-inheritance/controllers/ArticleController.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '../../../src/decorator/Controller'; 2 | import { AbstractControllerTemplate } from './AbstractContollerTemplate'; 3 | import { MockedRepository } from '../repository/MockedRepository'; 4 | 5 | const domain = 'article'; 6 | 7 | @Controller(`/${domain}`) 8 | export class ArticleController extends AbstractControllerTemplate { 9 | protected constructor() { 10 | super(); 11 | 12 | this.domain = domain; 13 | this.repository = new MockedRepository(domain); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sample/sample17-controllers-inheritance/controllers/CategoryController.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '../../../src/decorator/Controller'; 2 | import { AbstractControllerTemplate } from './AbstractContollerTemplate'; 3 | import { MockedRepository } from '../repository/MockedRepository'; 4 | 5 | const domain = 'category'; 6 | 7 | @Controller(`/${domain}`) 8 | export class CategoryController extends AbstractControllerTemplate { 9 | protected constructor() { 10 | super(); 11 | 12 | this.domain = domain; 13 | this.repository = new MockedRepository(domain); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sample/sample17-controllers-inheritance/controllers/ProductController.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '../../../src/decorator/Controller'; 2 | import { AbstractControllerTemplate } from './AbstractContollerTemplate'; 3 | import { MockedRepository } from '../repository/MockedRepository'; 4 | 5 | const domain = 'product'; 6 | 7 | @Controller(`/${domain}`) 8 | export class ProductController extends AbstractControllerTemplate { 9 | protected constructor() { 10 | super(); 11 | 12 | this.domain = domain; 13 | this.repository = new MockedRepository(domain); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sample/sample17-controllers-inheritance/interface/IInstance.ts: -------------------------------------------------------------------------------- 1 | export interface IInstance { 2 | id: number; 3 | type: string; 4 | } 5 | -------------------------------------------------------------------------------- /sample/sample17-controllers-inheritance/interface/IPayload.ts: -------------------------------------------------------------------------------- 1 | export interface IPayload { 2 | id: number; 3 | } 4 | -------------------------------------------------------------------------------- /sample/sample17-controllers-inheritance/repository/MockedRepository.ts: -------------------------------------------------------------------------------- 1 | import { IInstance } from '../interface/IInstance'; 2 | import { IPayload } from '../interface/IPayload'; 3 | 4 | export class MockedRepository { 5 | protected domain: string; 6 | 7 | constructor(domain: string) { 8 | this.domain = domain; 9 | } 10 | 11 | /** 12 | * @description Dummy method to return collection of items 13 | */ 14 | public getCollection(): Promise { 15 | return Promise.resolve([ 16 | { 17 | id: 10020, 18 | type: this.domain, 19 | }, 20 | { 21 | id: 10001, 22 | type: this.domain, 23 | }, 24 | { 25 | id: 10002, 26 | type: this.domain, 27 | }, 28 | ]); 29 | } 30 | 31 | /** 32 | * @description Dummy method to create a new item in storage and return its instance 33 | */ 34 | public create(payload: IPayload): Promise { 35 | return Promise.resolve({ 36 | id: 10000, 37 | type: this.domain, 38 | }); 39 | } 40 | 41 | /** 42 | * @description Dummy method to find item in storage 43 | */ 44 | public find(id: number): Promise { 45 | return Promise.resolve({ 46 | id: id, 47 | type: this.domain, 48 | }); 49 | } 50 | 51 | /** 52 | * @description Dummy method to delete item in storage by id 53 | */ 54 | public delete(id: number): Promise { 55 | return Promise.resolve(); 56 | } 57 | 58 | /** 59 | * @description Dummy method to update item in storage by id 60 | */ 61 | public update(id: number, payload: IPayload): Promise { 62 | return Promise.resolve({ 63 | id: 10000, 64 | type: this.domain, 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sample/sample2-controllers-from-directory/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import { useExpressServer } from '../../src/index'; 3 | 4 | let app = express(); // create express server 5 | useExpressServer(app, { 6 | controllers: [__dirname + '/controllers/*{.js,.ts}'], // register controllers routes in our express app 7 | }); 8 | app.listen(3001); // run express app 9 | 10 | console.log( 11 | 'Express server is running on port 3001. Open http://localhost:3001/blogs/ or http://localhost:3002/posts/' 12 | ); 13 | -------------------------------------------------------------------------------- /sample/sample2-controllers-from-directory/controllers/BlogController.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { JsonController } from '../../../src/decorator/JsonController'; 3 | import { Get } from '../../../src/decorator/Get'; 4 | import { Req } from '../../../src/index'; 5 | import { Post } from '../../../src/decorator/Post'; 6 | import { Put } from '../../../src/decorator/Put'; 7 | import { Patch } from '../../../src/decorator/Patch'; 8 | import { Delete } from '../../../src/decorator/Delete'; 9 | 10 | @JsonController() 11 | export class BlogController { 12 | @Get('/blogs') 13 | getAll() { 14 | return [ 15 | { id: 1, name: 'First blog!' }, 16 | { id: 2, name: 'Second blog!' }, 17 | ]; 18 | } 19 | 20 | @Get('/blogs/:id') 21 | getOne() { 22 | return { id: 1, name: 'First blog!' }; 23 | } 24 | 25 | @Post('/blogs') 26 | post(@Req() request: Request) { 27 | let blog = JSON.stringify(request.body); 28 | return 'Blog ' + blog + ' !saved!'; 29 | } 30 | 31 | @Put('/blogs/:id') 32 | put(@Req() request: Request) { 33 | return 'Blog #' + request.params.id + ' has been putted!'; 34 | } 35 | 36 | @Patch('/blogs/:id') 37 | patch(@Req() request: Request) { 38 | return 'Blog #' + request.params.id + ' has been patched!'; 39 | } 40 | 41 | @Delete('/blogs/:id') 42 | remove(@Req() request: Request) { 43 | return 'Blog #' + request.params.id + ' has been removed!'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sample/sample2-controllers-from-directory/controllers/PostController.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { Get } from '../../../src/decorator/Get'; 3 | import { Req } from '../../../src/index'; 4 | import { Post } from '../../../src/decorator/Post'; 5 | import { Put } from '../../../src/decorator/Put'; 6 | import { Patch } from '../../../src/decorator/Patch'; 7 | import { Delete } from '../../../src/decorator/Delete'; 8 | import { JsonController } from '../../../src/decorator/JsonController'; 9 | 10 | @JsonController() 11 | export class PostController { 12 | @Get('/posts') 13 | getAll() { 14 | return [ 15 | { id: 1, name: 'First post!' }, 16 | { id: 2, name: 'Second post!' }, 17 | ]; 18 | } 19 | 20 | @Get('/posts/:id') 21 | getOne() { 22 | return { id: 1, name: 'First post!' }; 23 | } 24 | 25 | @Post('/posts') 26 | post(@Req() request: Request) { 27 | let post = JSON.stringify(request.body); 28 | return 'Post ' + post + ' !saved!'; 29 | } 30 | 31 | @Put('/posts/:id') 32 | put(@Req() request: Request) { 33 | return 'Post #' + request.params.id + ' has been putted!'; 34 | } 35 | 36 | @Patch('/posts/:id') 37 | patch(@Req() request: Request) { 38 | return 'Post #' + request.params.id + ' has been patched!'; 39 | } 40 | 41 | @Delete('/posts/:id') 42 | remove(@Req() request: Request) { 43 | return 'Post #' + request.params.id + ' has been removed!'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sample/sample3-promise-support/BlogController.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { JsonController } from '../../src/decorator/JsonController'; 3 | import { Get } from '../../src/decorator/Get'; 4 | import { Req } from '../../src/index'; 5 | import { Post } from '../../src/decorator/Post'; 6 | import { Put } from '../../src/decorator/Put'; 7 | import { Patch } from '../../src/decorator/Patch'; 8 | import { Delete } from '../../src/decorator/Delete'; 9 | 10 | @JsonController() 11 | export class BlogController { 12 | @Get('/blogs') 13 | getAll() { 14 | return this.createPromise( 15 | [ 16 | { id: 1, name: 'Blog 1!' }, 17 | { id: 2, name: 'Blog 2!' }, 18 | ], 19 | 3000 20 | ); 21 | } 22 | 23 | @Get('/blogs/:id') 24 | getOne() { 25 | return this.createPromise({ id: 1, name: 'Blog 1!' }, 3000); 26 | } 27 | 28 | @Post('/blogs') 29 | post(@Req() request: Request) { 30 | let blog = JSON.stringify(request.body); 31 | return this.createPromise('Blog ' + blog + ' saved!', 3000); 32 | } 33 | 34 | @Put('/blogs/:id') 35 | put(@Req() request: Request) { 36 | return this.createPromise('Blog #' + request.params.id + ' has been putted!', 3000); 37 | } 38 | 39 | @Patch('/blogs/:id') 40 | patch(@Req() request: Request) { 41 | return this.createPromise('Blog #' + request.params.id + ' has been patched!', 3000); 42 | } 43 | 44 | @Delete('/blogs/:id') 45 | remove(@Req() request: Request) { 46 | return this.createPromise('Blog #' + request.params.id + ' has been removed!', 3000); 47 | } 48 | 49 | /** 50 | * Creates a fake promise with timeout. 51 | */ 52 | private createPromise(data: any, timeout: number): Promise { 53 | return new Promise((ok, fail) => { 54 | setTimeout(() => ok(data), timeout); 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /sample/sample3-promise-support/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import { useExpressServer } from '../../src/index'; 3 | import './BlogController'; // this can be require("./BlogController") actually 4 | 5 | let app = express(); // create express server 6 | useExpressServer(app); // register controllers routes in our express application 7 | // controllerRunner.isLogErrorsEnabled = true; // enable error logging of exception error into console 8 | // controllerRunner.isStackTraceEnabled = true; // enable adding of stack trace to response message 9 | 10 | app.listen(3001); // run express app 11 | 12 | console.log('Express server is running on port 3001. Open http://localhost:3001/blogs/'); 13 | -------------------------------------------------------------------------------- /sample/sample4-extra-parameters/BlogController.ts: -------------------------------------------------------------------------------- 1 | import { JsonController } from '../../src/decorator/JsonController'; 2 | import { Get } from '../../src/decorator/Get'; 3 | import { Post } from '../../src/decorator/Post'; 4 | import { Put } from '../../src/decorator/Put'; 5 | import { Patch } from '../../src/decorator/Patch'; 6 | import { Delete } from '../../src/decorator/Delete'; 7 | import { QueryParam } from '../../src/decorator/QueryParam'; 8 | import { Param } from '../../src/decorator/Param'; 9 | import { Body } from '../../src/decorator/Body'; 10 | 11 | export interface BlogFilter { 12 | keyword: string; 13 | limit: number; 14 | offset: number; 15 | } 16 | 17 | @JsonController() 18 | export class BlogController { 19 | @Get('/blogs') 20 | getAll(@QueryParam('filter', { required: true, parse: true }) filter: BlogFilter) { 21 | return [ 22 | { id: 1, name: 'Blog ' + filter.keyword }, 23 | { id: 2, name: 'Blog ' + filter.keyword }, 24 | ]; 25 | } 26 | 27 | @Get('/blogs/:id') 28 | getOne(@Param('id') id: number, @QueryParam('name') name: string) { 29 | return { id: id, name: name }; 30 | } 31 | 32 | @Post('/blogs') 33 | post(@Body() blog: any) { 34 | return 'Blog ' + JSON.stringify(blog) + ' !saved!'; 35 | } 36 | 37 | @Put('/blogs/:id') 38 | put(@Param('id') id: number) { 39 | return 'Blog #' + id + ' has been putted!'; 40 | } 41 | 42 | @Patch('/blogs/:id') 43 | patch(@Param('id') id: number) { 44 | return 'Blog #' + id + ' has been patched!'; 45 | } 46 | 47 | @Delete('/blogs/:id') 48 | remove(@Param('id') id: number) { 49 | return 'Blog #' + id + ' has been removed!'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /sample/sample4-extra-parameters/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import { useExpressServer } from '../../src/index'; 3 | 4 | require('./BlogController'); 5 | 6 | let app = express(); // create express server 7 | useExpressServer(app); // register loaded controllers in express app 8 | app.listen(3001); // run express app 9 | 10 | console.log('Express server is running on port 3001. Open http://localhost:3001/blogs/'); 11 | -------------------------------------------------------------------------------- /sample/sample5-http-errors/BlogController.ts: -------------------------------------------------------------------------------- 1 | import { JsonController } from '../../src/decorator/JsonController'; 2 | import { Get } from '../../src/decorator/Get'; 3 | import { ForbiddenError } from '../../src/http-error/ForbiddenError'; 4 | 5 | export class ValidationError extends Error { 6 | name = 'ValidationError'; 7 | message = 'Validation Error!'; 8 | errors = ['blank', 'minLength', 'maxLength']; 9 | } 10 | 11 | @JsonController() 12 | export class BlogController { 13 | @Get('/blogs') 14 | getAll() { 15 | throw new ForbiddenError('Nooooo this message will be lost'); 16 | } 17 | 18 | @Get('/blogs/:id') 19 | getOne() { 20 | throw new ValidationError(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/sample5-http-errors/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import { useExpressServer } from '../../src/index'; 3 | 4 | require('./BlogController'); 5 | 6 | let app = express(); // create express server 7 | useExpressServer(app, { 8 | errorOverridingMap: { 9 | ForbiddenError: { 10 | message: 'Access is denied', 11 | }, 12 | ValidationError: { 13 | httpCode: '400', 14 | message: 'Oops, Validation failed.', 15 | }, 16 | }, 17 | }); 18 | app.listen(3001); // run express app 19 | 20 | console.log('Express server is running on port 3001. Open http://localhost:3001/blogs/'); 21 | -------------------------------------------------------------------------------- /sample/sample6-global-middlewares/AllErrorsHandler.ts: -------------------------------------------------------------------------------- 1 | import { ExpressErrorMiddlewareInterface } from '../../src/driver/express/ExpressErrorMiddlewareInterface'; 2 | import { Middleware } from '../../src/decorator/Middleware'; 3 | 4 | @Middleware({ type: 'after' }) 5 | export class AllErrorsHandler implements ExpressErrorMiddlewareInterface { 6 | error(error: any, request: any, response: any, next: Function): void { 7 | console.log('Error handled: ', error); 8 | next(error); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/sample6-global-middlewares/BlogController.ts: -------------------------------------------------------------------------------- 1 | import { ForbiddenError } from '../../src/http-error/ForbiddenError'; 2 | import { Controller } from '../../src/decorator/Controller'; 3 | import { Get } from '../../src/decorator/Get'; 4 | import { Param } from '../../src/decorator/Param'; 5 | import { ContentType } from '../../src/decorator/ContentType'; 6 | 7 | @Controller() 8 | export class BlogController { 9 | @Get('/blogs') 10 | @ContentType('application/json') 11 | getAll() { 12 | console.log('hello blog'); 13 | return [ 14 | { id: 1, firstName: 'First', secondName: 'blog' }, 15 | { id: 2, firstName: 'Second', secondName: 'blog' }, 16 | ]; 17 | } 18 | 19 | @Get('/blogs/:id') 20 | @ContentType('application/json') 21 | getOne(@Param('id') id: number) { 22 | if (!id) throw new ForbiddenError(); 23 | 24 | return 'THIS STRING will BE not SO BIG'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/sample6-global-middlewares/CompressionMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../src/driver/express/ExpressMiddlewareInterface'; 2 | import { Middleware } from '../../src/decorator/Middleware'; 3 | 4 | @Middleware({ type: 'before' }) 5 | export class CompressionMiddleware implements ExpressMiddlewareInterface { 6 | use(request: any, response: any, next?: Function): any { 7 | console.log('hello compression ...'); 8 | next(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/sample6-global-middlewares/EndTimerMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../src/driver/express/ExpressMiddlewareInterface'; 2 | import { Middleware } from '../../src/decorator/Middleware'; 3 | 4 | @Middleware({ type: 'after' }) 5 | export class EndTimerMiddleware implements ExpressMiddlewareInterface { 6 | use(request: any, response: any, next?: Function): any { 7 | console.log('timer is ended.'); 8 | next(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/sample6-global-middlewares/LoggerMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../src/driver/express/ExpressMiddlewareInterface'; 2 | import { Middleware } from '../../src/decorator/Middleware'; 3 | 4 | @Middleware({ type: 'before' }) 5 | export class LoggerMiddleware implements ExpressMiddlewareInterface { 6 | use(request: any, response: any, next?: Function): any { 7 | console.log('logging request ...'); 8 | next(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/sample6-global-middlewares/StartTimerMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../src/driver/express/ExpressMiddlewareInterface'; 2 | import { Middleware } from '../../src/decorator/Middleware'; 3 | 4 | @Middleware({ type: 'before' }) 5 | export class StartTimerMiddleware implements ExpressMiddlewareInterface { 6 | use(request: any, response: any, next?: Function): any { 7 | console.log('timer is started.'); 8 | next(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/sample6-global-middlewares/app.ts: -------------------------------------------------------------------------------- 1 | import { createExpressServer } from '../../src/index'; 2 | import './BlogController'; 3 | import './CompressionMiddleware'; 4 | import './LoggerMiddleware'; 5 | import './StartTimerMiddleware'; 6 | import './EndTimerMiddleware'; 7 | import './AllErrorsHandler'; // same as: require("./BlogController"); 8 | // same as: require("./CompressionMiddleware"); 9 | // same as: require("./LoggerMiddleware"); 10 | // same as: require("./StartTimerMiddleware"); 11 | // same as: require("./EndTimerMiddleware"); 12 | // same as: require("./AllErrorsHandler"); 13 | 14 | const app = createExpressServer(); // register controller actions in express app 15 | app.listen(3001); // run express app 16 | 17 | console.log('Express server is running on port 3001. Open http://localhost:3001/blogs/'); 18 | -------------------------------------------------------------------------------- /sample/sample7-parsed-models/Photo.ts: -------------------------------------------------------------------------------- 1 | export class Photo { 2 | id: number; 3 | 4 | url: string; 5 | 6 | isUrlEmpty() { 7 | return !this.url; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample/sample7-parsed-models/User.ts: -------------------------------------------------------------------------------- 1 | import { Photo } from './Photo'; 2 | import { Exclude, Type } from 'class-transformer'; 3 | 4 | export class User { 5 | id: number; 6 | 7 | name: string; 8 | 9 | @Exclude() 10 | password: string; 11 | 12 | @Type(() => Photo) 13 | photo: Photo; 14 | } 15 | -------------------------------------------------------------------------------- /sample/sample7-parsed-models/UserController.ts: -------------------------------------------------------------------------------- 1 | import { JsonController } from '../../src/decorator/JsonController'; 2 | import { UserFilter } from './UserFilter'; 3 | import { User } from './User'; 4 | import { Get } from '../../src/decorator/Get'; 5 | import { Post } from '../../src/decorator/Post'; 6 | import { QueryParam } from '../../src/decorator/QueryParam'; 7 | import { Body } from '../../src/decorator/Body'; 8 | 9 | @JsonController() 10 | export class UserController { 11 | @Get('/users') 12 | getAll(@QueryParam('filter', { required: true, parse: true }) filter: UserFilter) { 13 | return filter.hasKeyword() ? 'filter has long keyword' : 'filter keyword is missing or too short'; 14 | } 15 | 16 | @Post('/users') 17 | post(@Body() user: User) { 18 | user.password = '1234abcd'; 19 | console.log('Is photo url empty?: ', user.photo.isUrlEmpty()); 20 | return user; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/sample7-parsed-models/UserFilter.ts: -------------------------------------------------------------------------------- 1 | export class UserFilter { 2 | keyword: string; 3 | 4 | hasKeyword(): boolean { 5 | return this.keyword && this.keyword.length > 2; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /sample/sample7-parsed-models/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import { useExpressServer } from '../../src/index'; 3 | 4 | require('./UserController'); 5 | 6 | let app = express(); // create express server 7 | useExpressServer(app); // register controllers routes in our express application 8 | app.listen(3001); // run express app 9 | 10 | console.log('Express server is running on port 3001. Open http://localhost:3001/users/'); 11 | -------------------------------------------------------------------------------- /sample/sample9-use-and-middlewares/AllControllerActionsMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../src/driver/express/ExpressMiddlewareInterface'; 2 | 3 | export class AllControllerActionsMiddleware implements ExpressMiddlewareInterface { 4 | use(request: any, response: any, next?: Function): any { 5 | console.log('controller action run'); 6 | next(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample/sample9-use-and-middlewares/BlogController.ts: -------------------------------------------------------------------------------- 1 | import { JsonController } from '../../src/decorator/JsonController'; 2 | import { Get } from '../../src/decorator/Get'; 3 | import { Param } from '../../src/decorator/Param'; 4 | import { CompressionMiddleware } from './CompressionMiddleware'; 5 | import { AllControllerActionsMiddleware } from './AllControllerActionsMiddleware'; 6 | import { UseBefore } from '../../src/decorator/UseBefore'; 7 | 8 | @JsonController() 9 | @UseBefore(AllControllerActionsMiddleware) 10 | export class BlogController { 11 | @Get('/blogs') 12 | @UseBefore(CompressionMiddleware) 13 | @UseBefore((request: any, response: any, next: Function) => { 14 | console.log('wow middleware'); 15 | next(); 16 | }) 17 | getAll() { 18 | console.log('hello blog'); 19 | return [ 20 | { id: 1, firstName: 'First', secondName: 'blog' }, 21 | { id: 2, firstName: 'Second', secondName: 'blog' }, 22 | ]; 23 | } 24 | 25 | @Get('/blogs/:id') 26 | getOne(@Param('id') id: number) { 27 | return { id: id, firstName: 'First', secondName: 'blog' }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sample/sample9-use-and-middlewares/CompressionMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../src/driver/express/ExpressMiddlewareInterface'; 2 | 3 | export class CompressionMiddleware implements ExpressMiddlewareInterface { 4 | use(request: any, response: any, next?: Function): any { 5 | console.log('hello compression ...'); 6 | next(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample/sample9-use-and-middlewares/app.ts: -------------------------------------------------------------------------------- 1 | import { createExpressServer } from '../../src/index'; 2 | import './BlogController'; // same as: require("./BlogController"); 3 | 4 | const app = createExpressServer(); // register controller actions in express app 5 | app.listen(3001); // run express app 6 | 7 | console.log('Express server is running on port 3001. Open http://localhost:3001/blogs/'); 8 | -------------------------------------------------------------------------------- /src/Action.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Controller action properties. 3 | */ 4 | export interface Action { 5 | /** 6 | * Action Request object. 7 | */ 8 | request: any; 9 | 10 | /** 11 | * Action Response object. 12 | */ 13 | response: any; 14 | 15 | /** 16 | * Content in which action is executed. 17 | * Koa-specific property. 18 | */ 19 | context?: any; 20 | 21 | /** 22 | * "Next" function used to call next middleware. 23 | */ 24 | next?: Function; 25 | } 26 | -------------------------------------------------------------------------------- /src/AuthorizationChecker.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './Action'; 2 | 3 | /** 4 | * Special function used to check user authorization roles per request. 5 | * Must return true or promise with boolean true resolved for authorization to succeed. 6 | */ 7 | export type AuthorizationChecker = (action: Action, roles: any[]) => Promise | boolean; 8 | -------------------------------------------------------------------------------- /src/CurrentUserChecker.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './Action'; 2 | 3 | /** 4 | * Special function used to get currently authorized user. 5 | */ 6 | export type CurrentUserChecker = (action: Action) => Promise | any; 7 | -------------------------------------------------------------------------------- /src/CustomParameterDecorator.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './Action'; 2 | 3 | /** 4 | * Used to register custom parameter handler in the controller action parameters. 5 | */ 6 | export interface CustomParameterDecorator { 7 | /** 8 | * Indicates if this parameter is required or not. 9 | * If parameter is required and value provided by it is not set then routing-controllers will throw an error. 10 | */ 11 | required?: boolean; 12 | 13 | /** 14 | * Factory function that returns value to be written to this parameter. 15 | * In function it provides you Action object which contains current request, response, context objects. 16 | * It also provides you original value of this parameter. 17 | * It can return promise, and if it returns promise then promise will be resolved before calling controller action. 18 | */ 19 | value: (action: Action, value?: any) => Promise | any; 20 | } 21 | -------------------------------------------------------------------------------- /src/InterceptorInterface.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './Action'; 2 | 3 | /** 4 | * Classes that intercepts response result must implement this interface. 5 | */ 6 | export interface InterceptorInterface { 7 | /** 8 | * Called before success response is being sent to the request. 9 | * Returned result will be sent to the user. 10 | */ 11 | intercept(action: Action, result: any): any | Promise; 12 | } 13 | -------------------------------------------------------------------------------- /src/RoleChecker.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './Action'; 2 | 3 | export interface RoleChecker { 4 | check(action: Action): boolean | Promise; 5 | } 6 | -------------------------------------------------------------------------------- /src/RoutingControllersOptions.ts: -------------------------------------------------------------------------------- 1 | import { ClassTransformOptions } from 'class-transformer'; 2 | import { ValidatorOptions } from 'class-validator'; 3 | import { AuthorizationChecker } from './AuthorizationChecker'; 4 | import { CurrentUserChecker } from './CurrentUserChecker'; 5 | 6 | /** 7 | * Routing controller initialization options. 8 | */ 9 | export interface RoutingControllersOptions { 10 | /** 11 | * Indicates if cors are enabled. 12 | * This requires installation of additional module (cors for express and @koa/cors for koa). 13 | */ 14 | cors?: boolean | Object; 15 | 16 | /** 17 | * Global route prefix, for example '/api'. 18 | */ 19 | routePrefix?: string; 20 | 21 | /** 22 | * List of controllers to register in the framework or directories from where to import all your controllers. 23 | */ 24 | controllers?: Function[] | string[]; 25 | 26 | /** 27 | * List of middlewares to register in the framework or directories from where to import all your middlewares. 28 | */ 29 | middlewares?: Function[] | string[]; 30 | 31 | /** 32 | * List of interceptors to register in the framework or directories from where to import all your interceptors. 33 | */ 34 | interceptors?: Function[] | string[]; 35 | 36 | /** 37 | * Indicates if class-transformer should be used to perform serialization / deserialization. 38 | */ 39 | classTransformer?: boolean; 40 | 41 | /** 42 | * Global class transformer options passed to class-transformer during classToPlain operation. 43 | * This operation is being executed when server returns response to user. 44 | */ 45 | classToPlainTransformOptions?: ClassTransformOptions; 46 | 47 | /** 48 | * Global class transformer options passed to class-transformer during plainToClass operation. 49 | * This operation is being executed when parsing user parameters. 50 | */ 51 | plainToClassTransformOptions?: ClassTransformOptions; 52 | 53 | /** 54 | * Indicates if class-validator should be used to auto validate objects injected into params. 55 | * You can also directly pass validator options to enable validator with a given options. 56 | */ 57 | validation?: boolean | ValidatorOptions; 58 | 59 | /** 60 | * Indicates if development mode is enabled. 61 | * By default its enabled if your NODE_ENV is not equal to "production". 62 | */ 63 | development?: boolean; 64 | 65 | /** 66 | * Indicates if default routing-controller's error handler is enabled or not. 67 | * Enabled by default. 68 | */ 69 | defaultErrorHandler?: boolean; 70 | 71 | /** 72 | * Map of error overrides. 73 | */ 74 | errorOverridingMap?: { [key: string]: any }; 75 | 76 | /** 77 | * Special function used to check user authorization roles per request. 78 | * Must return true or promise with boolean true resolved for authorization to succeed. 79 | */ 80 | authorizationChecker?: AuthorizationChecker; 81 | 82 | /** 83 | * Special function used to get currently authorized user. 84 | */ 85 | currentUserChecker?: CurrentUserChecker; 86 | 87 | /** 88 | * Default settings 89 | */ 90 | defaults?: { 91 | /** 92 | * If set, all null responses will return specified status code by default 93 | */ 94 | nullResultCode?: number; 95 | 96 | /** 97 | * If set, all undefined responses will return specified status code by default 98 | */ 99 | undefinedResultCode?: number; 100 | 101 | /** 102 | * Default param options 103 | */ 104 | paramOptions?: { 105 | /** 106 | * If true, all non-set parameters will be required by default 107 | */ 108 | required?: boolean; 109 | }; 110 | }; 111 | } 112 | -------------------------------------------------------------------------------- /src/container.ts: -------------------------------------------------------------------------------- 1 | import { Action } from './Action'; 2 | 3 | /** 4 | * Container options. 5 | */ 6 | export interface UseContainerOptions { 7 | /** 8 | * If set to true, then default container will be used in the case if given container haven't returned anything. 9 | */ 10 | fallback?: boolean; 11 | 12 | /** 13 | * If set to true, then default container will be used in the case if given container thrown an exception. 14 | */ 15 | fallbackOnErrors?: boolean; 16 | } 17 | 18 | export type ClassConstructor = { new (...args: any[]): T }; 19 | 20 | /** 21 | * Container to be used by this library for inversion control. If container was not implicitly set then by default 22 | * container simply creates a new instance of the given class. 23 | */ 24 | const defaultContainer: { get(someClass: ClassConstructor | Function): T } = new (class { 25 | private instances: { type: Function; object: any }[] = []; 26 | get(someClass: ClassConstructor): T { 27 | let instance = this.instances.find(instance => instance.type === someClass); 28 | if (!instance) { 29 | instance = { type: someClass, object: new someClass() }; 30 | this.instances.push(instance); 31 | } 32 | 33 | return instance.object; 34 | } 35 | })(); 36 | 37 | let userContainer: { get(someClass: ClassConstructor | Function, action?: Action): T }; 38 | let userContainerOptions: UseContainerOptions; 39 | 40 | /** 41 | * Allows routing controllers to resolve objects using your IoC container 42 | */ 43 | export interface IocAdapter { 44 | /** 45 | * Return 46 | */ 47 | get(someClass: ClassConstructor, action?: Action): T; 48 | } 49 | 50 | /** 51 | * Sets container to be used by this library. 52 | */ 53 | export function useContainer(iocAdapter: IocAdapter, options?: UseContainerOptions) { 54 | userContainer = iocAdapter; 55 | userContainerOptions = options; 56 | } 57 | 58 | /** 59 | * Gets the IOC container used by this library. 60 | * @param someClass A class constructor to resolve 61 | * @param action The request/response context that `someClass` is being resolved for 62 | */ 63 | export function getFromContainer(someClass: ClassConstructor | Function, action?: Action): T { 64 | if (userContainer) { 65 | try { 66 | const instance = userContainer.get(someClass, action); 67 | if (instance) return instance; 68 | 69 | if (!userContainerOptions || !userContainerOptions.fallback) return instance; 70 | } catch (error) { 71 | if (!userContainerOptions || !userContainerOptions.fallbackOnErrors) throw error; 72 | } 73 | } 74 | return defaultContainer.get(someClass); 75 | } 76 | -------------------------------------------------------------------------------- /src/decorator-options/BodyOptions.ts: -------------------------------------------------------------------------------- 1 | import { ValidatorOptions } from 'class-validator'; 2 | import { ClassTransformOptions } from 'class-transformer'; 3 | 4 | /** 5 | * Body decorator parameters. 6 | */ 7 | export interface BodyOptions { 8 | /** 9 | * If set to true then request body will become required. 10 | * If user performs a request and body is not in a request then routing-controllers will throw an error. 11 | */ 12 | required?: boolean; 13 | 14 | /** 15 | * Class-transformer options used to perform plainToClass operation. 16 | * 17 | * @see https://github.com/typestack/class-transformer 18 | */ 19 | transform?: ClassTransformOptions; 20 | 21 | /** 22 | * If true, class-validator will be used to validate param object. 23 | * If validation options are given then class-validator will perform validation with given options. 24 | * 25 | * @see https://github.com/typestack/class-validator 26 | */ 27 | validate?: boolean | ValidatorOptions; 28 | 29 | /** 30 | * Extra options to be passed to body-parser middleware. 31 | */ 32 | options?: any; 33 | 34 | /** 35 | * Explicitly set type which should be used for Body to perform transformation. 36 | */ 37 | type?: any; 38 | } 39 | -------------------------------------------------------------------------------- /src/decorator-options/ControllerOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extra options that apply to each controller action. 3 | */ 4 | export interface ControllerOptions { 5 | /** 6 | * If set to false, class-transformer won't be used to perform request serialization. 7 | */ 8 | transformRequest?: boolean; 9 | 10 | /** 11 | * If set to false, class-transformer won't be used to perform response serialization. 12 | */ 13 | transformResponse?: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /src/decorator-options/HandlerOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extra handler-specific options. 3 | */ 4 | export interface HandlerOptions { 5 | /** 6 | * If set to false, class-transformer won't be used to perform request serialization. 7 | */ 8 | transformRequest?: boolean; 9 | 10 | /** 11 | * If set to false, class-transformer won't be used to perform response serialization. 12 | */ 13 | transformResponse?: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /src/decorator-options/ParamOptions.ts: -------------------------------------------------------------------------------- 1 | import { ValidatorOptions } from 'class-validator'; 2 | import { ClassTransformOptions } from 'class-transformer'; 3 | 4 | /** 5 | * Extra options set to the parameter decorators. 6 | */ 7 | export interface ParamOptions { 8 | /** 9 | * If set to true then parameter will be required. 10 | * If user performs a request and required parameter is not in a request then routing-controllers will throw an error. 11 | */ 12 | required?: boolean; 13 | 14 | /** 15 | * If set to true then parameter will be parsed to json. 16 | * Parsing is automatically done if parameter type is a class type. 17 | */ 18 | parse?: boolean; 19 | 20 | /** 21 | * Class transform options used to perform plainToClass operation. 22 | */ 23 | transform?: ClassTransformOptions; 24 | 25 | /** 26 | * If true, class-validator will be used to validate param object. 27 | * If validation options are given then class-validator will perform validation with given options. 28 | */ 29 | validate?: boolean | ValidatorOptions; 30 | 31 | /** 32 | * Explicitly set type which should be used for param to perform transformation. 33 | */ 34 | type?: any; 35 | 36 | /** 37 | * Force value to be cast as an array. 38 | */ 39 | isArray?: boolean; 40 | } 41 | -------------------------------------------------------------------------------- /src/decorator-options/UploadOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Upload decorator parameters. 3 | */ 4 | export interface UploadOptions { 5 | /** 6 | * If set to true then uploaded file become required. 7 | * If user performs a request and file is not in a request then routing-controllers will throw an error. 8 | */ 9 | required?: boolean; 10 | 11 | /** 12 | * Special upload options passed to an upload middleware. 13 | */ 14 | options?: any; 15 | } 16 | -------------------------------------------------------------------------------- /src/decorator/All.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | import { ControllerOptions } from '../decorator-options/ControllerOptions'; 3 | 4 | /** 5 | * Registers an action to be executed when a request comes on a given route. 6 | * Must be applied on a controller action. 7 | */ 8 | export function All(route?: RegExp): Function; 9 | 10 | /** 11 | * Registers an action to be executed when a request comes on a given route. 12 | * Must be applied on a controller action. 13 | */ 14 | export function All(route?: string): Function; 15 | 16 | /** 17 | * Registers an action to be executed when a request comes on a given route. 18 | * Must be applied on a controller action. 19 | */ 20 | export function All(route?: string | RegExp, options?: ControllerOptions): Function { 21 | return function (object: Object, methodName: string) { 22 | getMetadataArgsStorage().actions.push({ 23 | type: 'all', 24 | target: object.constructor, 25 | method: methodName, 26 | route: route, 27 | options, 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/decorator/Authorized.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Marks controller action to have a special access. 5 | * Authorization logic must be defined in routing-controllers settings. 6 | */ 7 | export function Authorized(): Function; 8 | 9 | /** 10 | * Marks controller action to have a special access. 11 | * Authorization logic must be defined in routing-controllers settings. 12 | */ 13 | export function Authorized(role: any): Function; 14 | 15 | /** 16 | * Marks controller action to have a special access. 17 | * Authorization logic must be defined in routing-controllers settings. 18 | */ 19 | export function Authorized(roles: any[]): Function; 20 | 21 | /** 22 | * Marks controller action to have a special access. 23 | * Authorization logic must be defined in routing-controllers settings. 24 | */ 25 | export function Authorized(role: Function): Function; 26 | 27 | /** 28 | * Marks controller action to have a special access. 29 | * Authorization logic must be defined in routing-controllers settings. 30 | */ 31 | export function Authorized(roleOrRoles?: string | string[] | Function): Function { 32 | return function (clsOrObject: Function | Object, method?: string) { 33 | getMetadataArgsStorage().responseHandlers.push({ 34 | type: 'authorized', 35 | target: method ? clsOrObject.constructor : (clsOrObject as Function), 36 | method: method, 37 | value: roleOrRoles, 38 | }); 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/decorator/Body.ts: -------------------------------------------------------------------------------- 1 | import { BodyOptions } from '../decorator-options/BodyOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Allows to inject a request body value to the controller action parameter. 6 | * Must be applied on a controller action parameter. 7 | */ 8 | export function Body(options?: BodyOptions): Function { 9 | return function (object: Object, methodName: string, index: number) { 10 | getMetadataArgsStorage().params.push({ 11 | type: 'body', 12 | object: object, 13 | method: methodName, 14 | index: index, 15 | parse: false, 16 | required: options ? options.required : undefined, 17 | classTransform: options ? options.transform : undefined, 18 | validate: options ? options.validate : undefined, 19 | explicitType: options ? options.type : undefined, 20 | extraOptions: options ? options.options : undefined, 21 | }); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/decorator/BodyParam.ts: -------------------------------------------------------------------------------- 1 | import { ParamOptions } from '../decorator-options/ParamOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Takes partial data of the request body. 6 | * Must be applied on a controller action parameter. 7 | */ 8 | export function BodyParam(name: string, options?: ParamOptions): Function { 9 | return function (object: Object, methodName: string, index: number) { 10 | getMetadataArgsStorage().params.push({ 11 | type: 'body-param', 12 | object: object, 13 | method: methodName, 14 | index: index, 15 | name: name, 16 | parse: options ? options.parse : false, 17 | required: options ? options.required : undefined, 18 | explicitType: options ? options.type : undefined, 19 | classTransform: options ? options.transform : undefined, 20 | validate: options ? options.validate : undefined, 21 | }); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/decorator/ContentType.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Sets response Content-Type. 5 | * Must be applied on a controller action. 6 | */ 7 | export function ContentType(contentType: string): Function { 8 | return function (object: Object, methodName: string) { 9 | getMetadataArgsStorage().responseHandlers.push({ 10 | type: 'content-type', 11 | target: object.constructor, 12 | method: methodName, 13 | value: contentType, 14 | }); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/decorator/Controller.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | import { ControllerOptions } from '../decorator-options/ControllerOptions'; 3 | 4 | /** 5 | * Defines a class as a controller. 6 | * Each decorated controller method is served as a controller action. 7 | * Controller actions are executed when request come. 8 | * 9 | * @param baseRoute Extra path you can apply as a base route to all controller actions 10 | * @param options Extra options that apply to all controller actions 11 | */ 12 | export function Controller(baseRoute?: string, options?: ControllerOptions): Function { 13 | return function (object: Function) { 14 | getMetadataArgsStorage().controllers.push({ 15 | type: 'default', 16 | target: object, 17 | route: baseRoute, 18 | options, 19 | }); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/decorator/CookieParam.ts: -------------------------------------------------------------------------------- 1 | import { ParamOptions } from '../decorator-options/ParamOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Injects a request's cookie value to the controller action parameter. 6 | * Must be applied on a controller action parameter. 7 | */ 8 | export function CookieParam(name: string, options?: ParamOptions) { 9 | return function (object: Object, methodName: string, index: number) { 10 | getMetadataArgsStorage().params.push({ 11 | type: 'cookie', 12 | object: object, 13 | method: methodName, 14 | index: index, 15 | name: name, 16 | parse: options ? options.parse : false, 17 | required: options ? options.required : undefined, 18 | explicitType: options ? options.type : undefined, 19 | classTransform: options ? options.transform : undefined, 20 | validate: options ? options.validate : undefined, 21 | }); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/decorator/CookieParams.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Injects all request's cookies to the controller action parameter. 5 | * Must be applied on a controller action parameter. 6 | */ 7 | export function CookieParams() { 8 | return function (object: Object, methodName: string, index: number) { 9 | getMetadataArgsStorage().params.push({ 10 | type: 'cookies', 11 | object: object, 12 | method: methodName, 13 | index: index, 14 | parse: false, 15 | required: false, 16 | }); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/decorator/Ctx.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Injects a Koa's Context object to the controller action parameter. 5 | * Must be applied on a controller action parameter. 6 | */ 7 | export function Ctx(): Function { 8 | return function (object: Object, methodName: string, index: number) { 9 | getMetadataArgsStorage().params.push({ 10 | type: 'context', 11 | object: object, 12 | method: methodName, 13 | index: index, 14 | parse: false, 15 | required: false, 16 | }); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/decorator/CurrentUser.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Injects currently authorized user. 5 | * Authorization logic must be defined in routing-controllers settings. 6 | */ 7 | export function CurrentUser(options?: { required?: boolean }) { 8 | return function (object: Object, methodName: string, index: number) { 9 | getMetadataArgsStorage().params.push({ 10 | type: 'current-user', 11 | object: object, 12 | method: methodName, 13 | index: index, 14 | parse: false, 15 | required: options ? options.required : undefined, 16 | }); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/decorator/Delete.ts: -------------------------------------------------------------------------------- 1 | import { HandlerOptions } from '../decorator-options/HandlerOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Registers a controller method to be executed when DELETE request comes on a given route. 6 | * Must be applied on a controller action. 7 | */ 8 | export function Delete(route?: RegExp, options?: HandlerOptions): Function; 9 | 10 | /** 11 | * Registers a controller method to be executed when DELETE request comes on a given route. 12 | * Must be applied on a controller action. 13 | */ 14 | export function Delete(route?: string, options?: HandlerOptions): Function; 15 | 16 | /** 17 | * Registers a controller method to be executed when DELETE request comes on a given route. 18 | * Must be applied on a controller action. 19 | */ 20 | export function Delete(route?: string | RegExp, options?: HandlerOptions): Function { 21 | return function (object: Object, methodName: string) { 22 | getMetadataArgsStorage().actions.push({ 23 | type: 'delete', 24 | target: object.constructor, 25 | method: methodName, 26 | route: route, 27 | options, 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/decorator/Get.ts: -------------------------------------------------------------------------------- 1 | import { HandlerOptions } from '../decorator-options/HandlerOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Registers an action to be executed when GET request comes on a given route. 6 | * Must be applied on a controller action. 7 | */ 8 | export function Get(route?: RegExp, options?: HandlerOptions): Function; 9 | 10 | /** 11 | * Registers an action to be executed when GET request comes on a given route. 12 | * Must be applied on a controller action. 13 | */ 14 | export function Get(route?: string, options?: HandlerOptions): Function; 15 | 16 | /** 17 | * Registers an action to be executed when GET request comes on a given route. 18 | * Must be applied on a controller action. 19 | */ 20 | export function Get(route?: string | RegExp, options?: HandlerOptions): Function { 21 | return function (object: Object, methodName: string) { 22 | getMetadataArgsStorage().actions.push({ 23 | type: 'get', 24 | target: object.constructor, 25 | method: methodName, 26 | options, 27 | route, 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/decorator/Head.ts: -------------------------------------------------------------------------------- 1 | import { HandlerOptions } from '../decorator-options/HandlerOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Registers an action to be executed when HEAD request comes on a given route. 6 | * Must be applied on a controller action. 7 | */ 8 | export function Head(route?: RegExp, options?: HandlerOptions): Function; 9 | 10 | /** 11 | * Registers an action to be executed when HEAD request comes on a given route. 12 | * Must be applied on a controller action. 13 | */ 14 | export function Head(route?: string, options?: HandlerOptions): Function; 15 | 16 | /** 17 | * Registers an action to be executed when HEAD request comes on a given route. 18 | * Must be applied on a controller action. 19 | */ 20 | export function Head(route?: string | RegExp, options?: HandlerOptions): Function { 21 | return function (object: Object, methodName: string) { 22 | getMetadataArgsStorage().actions.push({ 23 | type: 'head', 24 | target: object.constructor, 25 | method: methodName, 26 | options, 27 | route, 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/decorator/Header.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Sets response header. 5 | * Must be applied on a controller action. 6 | */ 7 | export function Header(name: string, value: string): Function { 8 | return function (object: Object, methodName: string) { 9 | getMetadataArgsStorage().responseHandlers.push({ 10 | type: 'header', 11 | target: object.constructor, 12 | method: methodName, 13 | value: name, 14 | secondaryValue: value, 15 | }); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/decorator/HeaderParam.ts: -------------------------------------------------------------------------------- 1 | import { ParamOptions } from '../decorator-options/ParamOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Injects a request's http header value to the controller action parameter. 6 | * Must be applied on a controller action parameter. 7 | */ 8 | export function HeaderParam(name: string, options?: ParamOptions): Function { 9 | return function (object: Object, methodName: string, index: number) { 10 | getMetadataArgsStorage().params.push({ 11 | type: 'header', 12 | object: object, 13 | method: methodName, 14 | index: index, 15 | name: name, 16 | parse: options ? options.parse : false, 17 | required: options ? options.required : undefined, 18 | classTransform: options ? options.transform : undefined, 19 | explicitType: options ? options.type : undefined, 20 | validate: options ? options.validate : undefined, 21 | }); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/decorator/HeaderParams.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Injects all request's http headers to the controller action parameter. 5 | * Must be applied on a controller action parameter. 6 | */ 7 | export function HeaderParams(): Function { 8 | return function (object: Object, methodName: string, index: number) { 9 | getMetadataArgsStorage().params.push({ 10 | type: 'headers', 11 | object: object, 12 | method: methodName, 13 | index: index, 14 | parse: false, 15 | required: false, 16 | }); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/decorator/HttpCode.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Sets response HTTP status code. 5 | * Http code will be set only when controller action is successful. 6 | * In the case if controller action rejects or throws an exception http code won't be applied. 7 | * Must be applied on a controller action. 8 | */ 9 | export function HttpCode(code: number): Function { 10 | return function (object: Object, methodName: string) { 11 | getMetadataArgsStorage().responseHandlers.push({ 12 | type: 'success-code', 13 | target: object.constructor, 14 | method: methodName, 15 | value: code, 16 | }); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/decorator/Interceptor.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Registers a global interceptor. 5 | */ 6 | export function Interceptor(options?: { priority?: number }): Function { 7 | return function (target: Function) { 8 | getMetadataArgsStorage().interceptors.push({ 9 | target: target, 10 | global: true, 11 | priority: options && options.priority ? options.priority : 0, 12 | }); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/decorator/JsonController.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | import { ControllerOptions } from '../decorator-options/ControllerOptions'; 3 | 4 | /** 5 | * Defines a class as a JSON controller. If JSON controller is used, then all controller actions will return 6 | * a serialized json data, and its response content-type always will be application/json. 7 | * 8 | * @param baseRoute Extra path you can apply as a base route to all controller actions 9 | * @param options Extra options that apply to all controller actions 10 | */ 11 | export function JsonController(baseRoute?: string, options?: ControllerOptions) { 12 | return function (object: Function) { 13 | getMetadataArgsStorage().controllers.push({ 14 | type: 'json', 15 | target: object, 16 | route: baseRoute, 17 | options, 18 | }); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/decorator/Location.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Sets Location header with given value to the response. 5 | * Must be applied on a controller action. 6 | */ 7 | export function Location(url: string): Function { 8 | return function (object: Object, methodName: string) { 9 | getMetadataArgsStorage().responseHandlers.push({ 10 | type: 'location', 11 | target: object.constructor, 12 | method: methodName, 13 | value: url, 14 | }); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/decorator/Method.ts: -------------------------------------------------------------------------------- 1 | import { HandlerOptions } from '../decorator-options/HandlerOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | import { ActionType } from '../metadata/types/ActionType'; 4 | 5 | /** 6 | * Registers an action to be executed when request with specified method comes on a given route. 7 | * Must be applied on a controller action. 8 | */ 9 | export function Method(method: ActionType, route?: RegExp, options?: HandlerOptions): Function; 10 | 11 | /** 12 | * Registers an action to be executed when request with specified method comes on a given route. 13 | * Must be applied on a controller action. 14 | */ 15 | export function Method(method: ActionType, route?: string, options?: HandlerOptions): Function; 16 | 17 | /** 18 | * Registers an action to be executed when request with specified method comes on a given route. 19 | * Must be applied on a controller action. 20 | */ 21 | export function Method(method: ActionType, route?: string | RegExp, options?: HandlerOptions): Function { 22 | return function (object: Object, methodName: string) { 23 | getMetadataArgsStorage().actions.push({ 24 | type: method, 25 | target: object.constructor, 26 | method: methodName, 27 | options, 28 | route, 29 | }); 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/decorator/Middleware.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Marks given class as a middleware. 5 | * Allows to create global middlewares and control order of middleware execution. 6 | */ 7 | export function Middleware(options: { type: 'after' | 'before'; priority?: number }): Function { 8 | return function (target: Function) { 9 | getMetadataArgsStorage().middlewares.push({ 10 | target: target, 11 | type: options && options.type ? options.type : 'before', 12 | global: true, 13 | priority: options && options.priority !== undefined ? options.priority : 0, 14 | }); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/decorator/OnNull.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Used to set specific HTTP status code when result returned by a controller action is equal to null. 5 | * Must be applied on a controller action. 6 | */ 7 | export function OnNull(code: number): Function; 8 | 9 | /** 10 | * Used to set specific HTTP status code when result returned by a controller action is equal to null. 11 | * Must be applied on a controller action. 12 | */ 13 | export function OnNull(error: Function): Function; 14 | 15 | /** 16 | * Used to set specific HTTP status code when result returned by a controller action is equal to null. 17 | * Must be applied on a controller action. 18 | */ 19 | export function OnNull(codeOrError: number | Function): Function { 20 | return function (object: Object, methodName: string) { 21 | getMetadataArgsStorage().responseHandlers.push({ 22 | type: 'on-null', 23 | target: object.constructor, 24 | method: methodName, 25 | value: codeOrError, 26 | }); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/decorator/OnUndefined.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Used to set specific HTTP status code when result returned by a controller action is equal to undefined. 5 | * Must be applied on a controller action. 6 | */ 7 | export function OnUndefined(code: number): Function; 8 | 9 | /** 10 | * Used to set specific HTTP status code when result returned by a controller action is equal to undefined. 11 | * Must be applied on a controller action. 12 | */ 13 | export function OnUndefined(error: Function): Function; 14 | 15 | /** 16 | * Used to set specific HTTP status code when result returned by a controller action is equal to undefined. 17 | * Must be applied on a controller action. 18 | */ 19 | export function OnUndefined(codeOrError: number | Function): Function { 20 | return function (object: Object, methodName: string) { 21 | getMetadataArgsStorage().responseHandlers.push({ 22 | type: 'on-undefined', 23 | target: object.constructor, 24 | method: methodName, 25 | value: codeOrError, 26 | }); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/decorator/Param.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Injects a request's route parameter value to the controller action parameter. 5 | * Must be applied on a controller action parameter. 6 | */ 7 | export function Param(name: string): Function { 8 | return function (object: Object, methodName: string, index: number) { 9 | getMetadataArgsStorage().params.push({ 10 | type: 'param', 11 | object: object, 12 | method: methodName, 13 | index: index, 14 | name: name, 15 | parse: false, // it does not make sense for Param to be parsed 16 | required: true, // params are always required, because if they are missing router will not match the route 17 | classTransform: undefined, 18 | }); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/decorator/Params.ts: -------------------------------------------------------------------------------- 1 | import { ParamOptions } from '../decorator-options/ParamOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Injects all request's route parameters to the controller action parameter. 6 | * Must be applied on a controller action parameter. 7 | */ 8 | export function Params(options?: ParamOptions): Function { 9 | return function (object: Object, methodName: string, index: number) { 10 | getMetadataArgsStorage().params.push({ 11 | type: 'params', 12 | object: object, 13 | method: methodName, 14 | index: index, 15 | parse: options ? options.parse : false, 16 | required: options ? options.required : undefined, 17 | classTransform: options ? options.transform : undefined, 18 | explicitType: options ? options.type : undefined, 19 | validate: options ? options.validate : undefined, 20 | }); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/decorator/Patch.ts: -------------------------------------------------------------------------------- 1 | import { HandlerOptions } from '../decorator-options/HandlerOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Registers an action to be executed when PATCH request comes on a given route. 6 | * Must be applied on a controller action. 7 | */ 8 | export function Patch(route?: RegExp, options?: HandlerOptions): Function; 9 | 10 | /** 11 | * Registers an action to be executed when PATCH request comes on a given route. 12 | * Must be applied on a controller action. 13 | */ 14 | export function Patch(route?: string, options?: HandlerOptions): Function; 15 | 16 | /** 17 | * Registers an action to be executed when PATCH request comes on a given route. 18 | * Must be applied on a controller action. 19 | */ 20 | export function Patch(route?: string | RegExp, options?: HandlerOptions): Function { 21 | return function (object: Object, methodName: string) { 22 | getMetadataArgsStorage().actions.push({ 23 | type: 'patch', 24 | target: object.constructor, 25 | method: methodName, 26 | route: route, 27 | options, 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/decorator/Post.ts: -------------------------------------------------------------------------------- 1 | import { HandlerOptions } from '../decorator-options/HandlerOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Registers an action to be executed when POST request comes on a given route. 6 | * Must be applied on a controller action. 7 | */ 8 | export function Post(route?: RegExp, options?: HandlerOptions): Function; 9 | 10 | /** 11 | * Registers an action to be executed when POST request comes on a given route. 12 | * Must be applied on a controller action. 13 | */ 14 | export function Post(route?: string, options?: HandlerOptions): Function; 15 | 16 | /** 17 | * Registers an action to be executed when POST request comes on a given route. 18 | * Must be applied on a controller action. 19 | */ 20 | export function Post(route?: string | RegExp, options?: HandlerOptions): Function { 21 | return function (object: Object, methodName: string) { 22 | getMetadataArgsStorage().actions.push({ 23 | type: 'post', 24 | target: object.constructor, 25 | method: methodName, 26 | options, 27 | route, 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/decorator/Put.ts: -------------------------------------------------------------------------------- 1 | import { HandlerOptions } from '../decorator-options/HandlerOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Registers an action to be executed when PUT request comes on a given route. 6 | * Must be applied on a controller action. 7 | */ 8 | export function Put(route?: RegExp, options?: HandlerOptions): Function; 9 | 10 | /** 11 | * Registers an action to be executed when PUT request comes on a given route. 12 | * Must be applied on a controller action. 13 | */ 14 | export function Put(route?: string, options?: HandlerOptions): Function; 15 | 16 | /** 17 | * Registers an action to be executed when PUT request comes on a given route. 18 | * Must be applied on a controller action. 19 | */ 20 | export function Put(route?: string | RegExp, options?: HandlerOptions): Function { 21 | return function (object: Object, methodName: string) { 22 | getMetadataArgsStorage().actions.push({ 23 | type: 'put', 24 | target: object.constructor, 25 | method: methodName, 26 | route: route, 27 | options, 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/decorator/QueryParam.ts: -------------------------------------------------------------------------------- 1 | import { ParamOptions } from '../decorator-options/ParamOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Injects a request's query parameter value to the controller action parameter. 6 | * Must be applied on a controller action parameter. 7 | */ 8 | export function QueryParam(name: string, options?: ParamOptions): Function { 9 | return function (object: Object, methodName: string, index: number) { 10 | getMetadataArgsStorage().params.push({ 11 | type: 'query', 12 | object: object, 13 | method: methodName, 14 | index: index, 15 | name: name, 16 | parse: options ? options.parse : false, 17 | required: options ? options.required : undefined, 18 | classTransform: options ? options.transform : undefined, 19 | explicitType: options ? options.type : undefined, 20 | validate: options ? options.validate : undefined, 21 | isArray: options?.isArray ?? false, 22 | }); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/decorator/QueryParams.ts: -------------------------------------------------------------------------------- 1 | import { ParamOptions } from '../decorator-options/ParamOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Injects all request's query parameters to the controller action parameter. 6 | * Must be applied on a controller action parameter. 7 | */ 8 | export function QueryParams(options?: ParamOptions): Function { 9 | return function (object: Object, methodName: string, index: number) { 10 | getMetadataArgsStorage().params.push({ 11 | type: 'queries', 12 | object: object, 13 | method: methodName, 14 | index: index, 15 | name: '', 16 | parse: options ? options.parse : false, 17 | required: options ? options.required : undefined, 18 | classTransform: options ? options.transform : undefined, 19 | explicitType: options ? options.type : undefined, 20 | validate: options ? options.validate : undefined, 21 | }); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/decorator/Redirect.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Sets Redirect header with given value to the response. 5 | * Must be applied on a controller action. 6 | */ 7 | export function Redirect(url: string): Function { 8 | return function (object: Object, methodName: string) { 9 | getMetadataArgsStorage().responseHandlers.push({ 10 | type: 'redirect', 11 | target: object.constructor, 12 | method: methodName, 13 | value: url, 14 | }); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/decorator/Render.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Specifies a template to be rendered by a controller action. 5 | * Must be applied on a controller action. 6 | */ 7 | export function Render(template: string): Function { 8 | return function (object: Object, methodName: string) { 9 | getMetadataArgsStorage().responseHandlers.push({ 10 | type: 'rendered-template', 11 | target: object.constructor, 12 | method: methodName, 13 | value: template, 14 | }); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/decorator/Req.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Injects a Request object to the controller action parameter. 5 | * Must be applied on a controller action parameter. 6 | */ 7 | export function Req(): Function { 8 | return function (object: Object, methodName: string, index: number) { 9 | getMetadataArgsStorage().params.push({ 10 | type: 'request', 11 | object: object, 12 | method: methodName, 13 | index: index, 14 | parse: false, 15 | required: false, 16 | }); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/decorator/Res.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Injects a Response object to the controller action parameter. 5 | * Must be applied on a controller action parameter. 6 | */ 7 | export function Res(): Function { 8 | return function (object: Object, methodName: string, index: number) { 9 | getMetadataArgsStorage().params.push({ 10 | type: 'response', 11 | object: object, 12 | method: methodName, 13 | index: index, 14 | parse: false, 15 | required: false, 16 | }); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/decorator/ResponseClassTransformOptions.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | import { ClassTransformOptions } from 'class-transformer'; 3 | 4 | /** 5 | * Options to be set to class-transformer for the result of the response. 6 | */ 7 | export function ResponseClassTransformOptions(options: ClassTransformOptions): Function { 8 | return function (object: Object, methodName: string) { 9 | getMetadataArgsStorage().responseHandlers.push({ 10 | type: 'response-class-transform-options', 11 | value: options, 12 | target: object.constructor, 13 | method: methodName, 14 | }); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/decorator/Session.ts: -------------------------------------------------------------------------------- 1 | import { ParamOptions } from '../decorator-options/ParamOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Injects a Session object to the controller action parameter. 6 | * Must be applied on a controller action parameter. 7 | */ 8 | export function Session(options?: ParamOptions): ParameterDecorator { 9 | return function (object: Object, methodName: string, index: number) { 10 | getMetadataArgsStorage().params.push({ 11 | type: 'session', 12 | object: object, 13 | method: methodName, 14 | index: index, 15 | parse: false, // it makes no sense for Session object to be parsed as json 16 | required: options && options.required !== undefined ? options.required : true, 17 | classTransform: options && options.transform, 18 | validate: options && options.validate !== undefined ? options.validate : false, 19 | }); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/decorator/SessionParam.ts: -------------------------------------------------------------------------------- 1 | import { ParamOptions } from '../decorator-options/ParamOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Injects a Session object property to the controller action parameter. 6 | * Must be applied on a controller action parameter. 7 | */ 8 | export function SessionParam(propertyName: string, options?: ParamOptions): ParameterDecorator { 9 | return function (object: Object, methodName: string, index: number) { 10 | getMetadataArgsStorage().params.push({ 11 | type: 'session-param', 12 | object: object, 13 | method: methodName, 14 | index: index, 15 | name: propertyName, 16 | parse: false, // it makes no sense for Session object to be parsed as json 17 | required: options && options.required !== undefined ? options.required : false, 18 | classTransform: options && options.transform, 19 | validate: options && options.validate !== undefined ? options.validate : false, 20 | }); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/decorator/State.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Injects a State object to the controller action parameter. 5 | * Must be applied on a controller action parameter. 6 | */ 7 | export function State(objectName?: string): Function { 8 | return function (object: Object, methodName: string, index: number) { 9 | getMetadataArgsStorage().params.push({ 10 | type: 'state', 11 | object: object, 12 | method: methodName, 13 | index: index, 14 | name: objectName, 15 | parse: false, // it does not make sense for Session to be parsed 16 | required: true, // when we demand session object, it must exist (working session middleware) 17 | classTransform: undefined, 18 | }); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/decorator/UploadedFile.ts: -------------------------------------------------------------------------------- 1 | import { UploadOptions } from '../decorator-options/UploadOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Injects an uploaded file object to the controller action parameter. 6 | * Must be applied on a controller action parameter. 7 | */ 8 | export function UploadedFile(name: string, options?: UploadOptions): Function { 9 | return function (object: Object, methodName: string, index: number) { 10 | getMetadataArgsStorage().params.push({ 11 | type: 'file', 12 | object: object, 13 | method: methodName, 14 | index: index, 15 | name: name, 16 | parse: false, 17 | required: options ? options.required : undefined, 18 | extraOptions: options ? options.options : undefined, 19 | }); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/decorator/UploadedFiles.ts: -------------------------------------------------------------------------------- 1 | import { UploadOptions } from '../decorator-options/UploadOptions'; 2 | import { getMetadataArgsStorage } from '../index'; 3 | 4 | /** 5 | * Injects all uploaded files to the controller action parameter. 6 | * Must be applied on a controller action parameter. 7 | */ 8 | export function UploadedFiles(name: string, options?: UploadOptions): Function { 9 | return function (object: Object, methodName: string, index: number) { 10 | getMetadataArgsStorage().params.push({ 11 | type: 'files', 12 | object: object, 13 | method: methodName, 14 | index: index, 15 | name: name, 16 | parse: false, 17 | required: options ? options.required : undefined, 18 | extraOptions: options ? options.options : undefined, 19 | }); 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/decorator/UseAfter.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Specifies a given middleware to be used for controller or controller action AFTER the action executes. 5 | * Must be set to controller action or controller class. 6 | */ 7 | export function UseAfter(...middlewares: Array): Function; 8 | 9 | /** 10 | * Specifies a given middleware to be used for controller or controller action AFTER the action executes. 11 | * Must be set to controller action or controller class. 12 | */ 13 | export function UseAfter(...middlewares: Array<(context: any, next: () => Promise) => Promise>): Function; 14 | 15 | /** 16 | * Specifies a given middleware to be used for controller or controller action AFTER the action executes. 17 | * Must be set to controller action or controller class. 18 | */ 19 | export function UseAfter(...middlewares: Array<(request: any, response: any, next: Function) => any>): Function; 20 | 21 | /** 22 | * Specifies a given middleware to be used for controller or controller action AFTER the action executes. 23 | * Must be set to controller action or controller class. 24 | */ 25 | export function UseAfter( 26 | ...middlewares: Array any)> 27 | ): Function { 28 | return function (objectOrFunction: Object | Function, methodName?: string) { 29 | middlewares.forEach(middleware => { 30 | getMetadataArgsStorage().uses.push({ 31 | target: methodName ? objectOrFunction.constructor : (objectOrFunction as Function), 32 | method: methodName, 33 | middleware: middleware, 34 | afterAction: true, 35 | }); 36 | }); 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/decorator/UseBefore.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | 3 | /** 4 | * Specifies a given middleware to be used for controller or controller action BEFORE the action executes. 5 | * Must be set to controller action or controller class. 6 | */ 7 | export function UseBefore(...middlewares: Array): Function; 8 | 9 | /** 10 | * Specifies a given middleware to be used for controller or controller action BEFORE the action executes. 11 | * Must be set to controller action or controller class. 12 | */ 13 | export function UseBefore(...middlewares: Array<(context: any, next: () => Promise) => Promise>): Function; 14 | 15 | /** 16 | * Specifies a given middleware to be used for controller or controller action BEFORE the action executes. 17 | * Must be set to controller action or controller class. 18 | */ 19 | export function UseBefore(...middlewares: Array<(request: any, response: any, next: Function) => any>): Function; 20 | 21 | /** 22 | * Specifies a given middleware to be used for controller or controller action BEFORE the action executes. 23 | * Must be set to controller action or controller class. 24 | */ 25 | export function UseBefore( 26 | ...middlewares: Array any)> 27 | ): Function { 28 | return function (objectOrFunction: Object | Function, methodName?: string) { 29 | middlewares.forEach(middleware => { 30 | getMetadataArgsStorage().uses.push({ 31 | target: methodName ? objectOrFunction.constructor : (objectOrFunction as Function), 32 | method: methodName, 33 | middleware: middleware, 34 | afterAction: false, 35 | }); 36 | }); 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/decorator/UseInterceptor.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataArgsStorage } from '../index'; 2 | import { Action } from '../Action'; 3 | 4 | /** 5 | * Specifies a given interceptor middleware or interceptor function to be used for controller or controller action. 6 | * Must be set to controller action or controller class. 7 | */ 8 | export function UseInterceptor(...interceptors: Array): Function; 9 | 10 | /** 11 | * Specifies a given interceptor middleware or interceptor function to be used for controller or controller action. 12 | * Must be set to controller action or controller class. 13 | */ 14 | export function UseInterceptor(...interceptors: Array<(action: Action, result: any) => any>): Function; 15 | 16 | /** 17 | * Specifies a given interceptor middleware or interceptor function to be used for controller or controller action. 18 | * Must be set to controller action or controller class. 19 | */ 20 | export function UseInterceptor(...interceptors: Array any)>): Function { 21 | return function (objectOrFunction: Object | Function, methodName?: string) { 22 | interceptors.forEach(interceptor => { 23 | getMetadataArgsStorage().useInterceptors.push({ 24 | interceptor: interceptor, 25 | target: methodName ? objectOrFunction.constructor : (objectOrFunction as Function), 26 | method: methodName, 27 | }); 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/driver/express/ExpressErrorMiddlewareInterface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Express error middlewares can implement this interface. 3 | */ 4 | export interface ExpressErrorMiddlewareInterface { 5 | /** 6 | * Called before response.send is being called. The data passed to method is the data passed to .send method. 7 | * Note that you must return same (or changed) data and it will be passed to .send method. 8 | */ 9 | error(error: any, request: any, response: any, next: (err?: any) => any): void; 10 | } 11 | -------------------------------------------------------------------------------- /src/driver/express/ExpressMiddlewareInterface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to register middlewares. 3 | * This signature is used for express middlewares. 4 | */ 5 | export interface ExpressMiddlewareInterface { 6 | /** 7 | * Called before controller action is being executed. 8 | * This signature is used for Express Middlewares. 9 | */ 10 | use(request: any, response: any, next: (err?: any) => any): any; 11 | } 12 | -------------------------------------------------------------------------------- /src/driver/koa/KoaMiddlewareInterface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to register middlewares. 3 | * This signature is used for koa middlewares. 4 | */ 5 | export interface KoaMiddlewareInterface { 6 | /** 7 | * Called before controller action is being executed. 8 | */ 9 | use(context: any, next: (err?: any) => Promise): Promise; 10 | } 11 | -------------------------------------------------------------------------------- /src/error/AccessDeniedError.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '../Action'; 2 | import { ForbiddenError } from '../http-error/ForbiddenError'; 3 | 4 | /** 5 | * Thrown when route is guarded by @Authorized decorator. 6 | */ 7 | export class AccessDeniedError extends ForbiddenError { 8 | name = 'AccessDeniedError'; 9 | 10 | constructor(action: Action) { 11 | super(); 12 | Object.setPrototypeOf(this, AccessDeniedError.prototype); 13 | const uri = `${action.request.method} ${action.request.url}`; // todo: check it it works in koa 14 | this.message = `Access is denied for request on ${uri}`; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/error/AuthorizationCheckerNotDefinedError.ts: -------------------------------------------------------------------------------- 1 | import { InternalServerError } from '../http-error/InternalServerError'; 2 | 3 | /** 4 | * Thrown when authorizationChecker function is not defined in routing-controllers options. 5 | */ 6 | export class AuthorizationCheckerNotDefinedError extends InternalServerError { 7 | name = 'AuthorizationCheckerNotDefinedError'; 8 | 9 | constructor() { 10 | super( 11 | `Cannot use @Authorized decorator. Please define authorizationChecker function in routing-controllers action before using it.` 12 | ); 13 | Object.setPrototypeOf(this, AuthorizationCheckerNotDefinedError.prototype); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/error/AuthorizationRequiredError.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '../Action'; 2 | import { UnauthorizedError } from '../http-error/UnauthorizedError'; 3 | 4 | /** 5 | * Thrown when authorization is required thought @CurrentUser decorator. 6 | */ 7 | export class AuthorizationRequiredError extends UnauthorizedError { 8 | name = 'AuthorizationRequiredError'; 9 | 10 | constructor(action: Action) { 11 | super(); 12 | Object.setPrototypeOf(this, AuthorizationRequiredError.prototype); 13 | const uri = `${action.request.method} ${action.request.url}`; // todo: check it it works in koa 14 | this.message = `Authorization is required for request on ${uri}`; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/error/CurrentUserCheckerNotDefinedError.ts: -------------------------------------------------------------------------------- 1 | import { InternalServerError } from '../http-error/InternalServerError'; 2 | 3 | /** 4 | * Thrown when currentUserChecker function is not defined in routing-controllers options. 5 | */ 6 | export class CurrentUserCheckerNotDefinedError extends InternalServerError { 7 | name = 'CurrentUserCheckerNotDefinedError'; 8 | 9 | constructor() { 10 | super( 11 | `Cannot use @CurrentUser decorator. Please define currentUserChecker function in routing-controllers action before using it.` 12 | ); 13 | Object.setPrototypeOf(this, CurrentUserCheckerNotDefinedError.prototype); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/error/ParamNormalizationError.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestError } from '../http-error/BadRequestError'; 2 | 3 | /** 4 | * Caused when user query parameter is invalid (cannot be parsed into selected type). 5 | */ 6 | export class InvalidParamError extends BadRequestError { 7 | name = 'ParamNormalizationError'; 8 | 9 | constructor(value: any, parameterName: string, parameterType: string) { 10 | super( 11 | `Given parameter ${parameterName} is invalid. Value (${JSON.stringify( 12 | value 13 | )}) cannot be parsed into ${parameterType}.` 14 | ); 15 | 16 | Object.setPrototypeOf(this, InvalidParamError.prototype); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/error/ParamRequiredError.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestError } from '../http-error/BadRequestError'; 2 | import { ParamMetadata } from '../metadata/ParamMetadata'; 3 | import { Action } from '../Action'; 4 | 5 | /** 6 | * Thrown when parameter is required, but was missing in a user request. 7 | */ 8 | export class ParamRequiredError extends BadRequestError { 9 | name = 'ParamRequiredError'; 10 | 11 | constructor(action: Action, param: ParamMetadata) { 12 | super(); 13 | Object.setPrototypeOf(this, ParamRequiredError.prototype); 14 | 15 | let paramName: string; 16 | switch (param.type) { 17 | case 'param': 18 | paramName = `Parameter "${param.name}" is`; 19 | break; 20 | 21 | case 'body': 22 | paramName = 'Request body is'; 23 | break; 24 | 25 | case 'body-param': 26 | paramName = `Body parameter "${param.name}" is`; 27 | break; 28 | 29 | case 'query': 30 | paramName = `Query parameter "${param.name}" is`; 31 | break; 32 | 33 | case 'header': 34 | paramName = `Header "${param.name}" is`; 35 | break; 36 | 37 | case 'file': 38 | paramName = `Uploaded file "${param.name}" is`; 39 | break; 40 | 41 | case 'files': 42 | paramName = `Uploaded files "${param.name}" are`; 43 | break; 44 | 45 | case 'session': 46 | paramName = 'Session is'; 47 | break; 48 | 49 | case 'cookie': 50 | paramName = 'Cookie is'; 51 | break; 52 | 53 | default: 54 | paramName = 'Parameter is'; 55 | } 56 | 57 | const uri = `${action.request.method} ${action.request.url}`; // todo: check it it works in koa 58 | this.message = `${paramName} required for request on ${uri}`; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/error/ParameterParseJsonError.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestError } from '../http-error/BadRequestError'; 2 | 3 | /** 4 | * Caused when user parameter is invalid json string and cannot be parsed. 5 | */ 6 | export class ParameterParseJsonError extends BadRequestError { 7 | name = 'ParameterParseJsonError'; 8 | 9 | constructor(parameterName: string, value: any) { 10 | super(`Given parameter ${parameterName} is invalid. Value (${JSON.stringify(value)}) cannot be parsed into JSON.`); 11 | Object.setPrototypeOf(this, ParameterParseJsonError.prototype); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/http-error/BadRequestError.ts: -------------------------------------------------------------------------------- 1 | import { HttpError } from './HttpError'; 2 | 3 | /** 4 | * Exception for 400 HTTP error. 5 | */ 6 | export class BadRequestError extends HttpError { 7 | name = 'BadRequestError'; 8 | 9 | constructor(message?: string) { 10 | super(400); 11 | Object.setPrototypeOf(this, BadRequestError.prototype); 12 | 13 | if (message) this.message = message; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/http-error/ForbiddenError.ts: -------------------------------------------------------------------------------- 1 | import { HttpError } from './HttpError'; 2 | 3 | /** 4 | * Exception for 403 HTTP error. 5 | */ 6 | export class ForbiddenError extends HttpError { 7 | name = 'ForbiddenError'; 8 | 9 | constructor(message?: string) { 10 | super(403); 11 | Object.setPrototypeOf(this, ForbiddenError.prototype); 12 | 13 | if (message) this.message = message; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/http-error/HttpError.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to throw HTTP errors. 3 | * Just do throw new HttpError(code, message) in your controller action and 4 | * default error handler will catch it and give in your response given code and message . 5 | */ 6 | export class HttpError extends Error { 7 | httpCode: number; 8 | 9 | constructor(httpCode: number, message?: string) { 10 | super(); 11 | Object.setPrototypeOf(this, HttpError.prototype); 12 | 13 | if (httpCode) this.httpCode = httpCode; 14 | if (message) this.message = message; 15 | 16 | this.stack = new Error().stack; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/http-error/InternalServerError.ts: -------------------------------------------------------------------------------- 1 | import { HttpError } from './HttpError'; 2 | 3 | /** 4 | * Exception for 500 HTTP error. 5 | */ 6 | export class InternalServerError extends HttpError { 7 | name = 'InternalServerError'; 8 | 9 | constructor(message: string) { 10 | super(500); 11 | Object.setPrototypeOf(this, InternalServerError.prototype); 12 | 13 | if (message) this.message = message; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/http-error/MethodNotAllowedError.ts: -------------------------------------------------------------------------------- 1 | import { HttpError } from './HttpError'; 2 | 3 | /** 4 | * Exception for todo HTTP error. 5 | */ 6 | export class MethodNotAllowedError extends HttpError { 7 | name = 'MethodNotAllowedError'; 8 | 9 | constructor(message?: string) { 10 | super(405); 11 | Object.setPrototypeOf(this, MethodNotAllowedError.prototype); 12 | 13 | if (message) this.message = message; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/http-error/NotAcceptableError.ts: -------------------------------------------------------------------------------- 1 | import { HttpError } from './HttpError'; 2 | 3 | /** 4 | * Exception for 406 HTTP error. 5 | */ 6 | export class NotAcceptableError extends HttpError { 7 | name = 'NotAcceptableError'; 8 | 9 | constructor(message?: string) { 10 | super(406); 11 | Object.setPrototypeOf(this, NotAcceptableError.prototype); 12 | 13 | if (message) this.message = message; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/http-error/NotFoundError.ts: -------------------------------------------------------------------------------- 1 | import { HttpError } from './HttpError'; 2 | 3 | /** 4 | * Exception for 404 HTTP error. 5 | */ 6 | export class NotFoundError extends HttpError { 7 | name = 'NotFoundError'; 8 | 9 | constructor(message?: string) { 10 | super(404); 11 | Object.setPrototypeOf(this, NotFoundError.prototype); 12 | 13 | if (message) this.message = message; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/http-error/UnauthorizedError.ts: -------------------------------------------------------------------------------- 1 | import { HttpError } from './HttpError'; 2 | 3 | /** 4 | * Exception for 401 HTTP error. 5 | */ 6 | export class UnauthorizedError extends HttpError { 7 | name = 'UnauthorizedError'; 8 | 9 | constructor(message?: string) { 10 | super(401); 11 | Object.setPrototypeOf(this, UnauthorizedError.prototype); 12 | 13 | if (message) this.message = message; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/http-error/UnprocessableEntityError.ts: -------------------------------------------------------------------------------- 1 | import { HttpError } from './HttpError'; 2 | 3 | /** 4 | * Exception for 422 HTTP error. 5 | */ 6 | export class UnprocessableEntityError extends HttpError { 7 | name = 'UnprocessableEntityError'; 8 | 9 | constructor(message?: string) { 10 | super(422); 11 | Object.setPrototypeOf(this, UnprocessableEntityError.prototype); 12 | 13 | if (message) this.message = message; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/metadata/ControllerMetadata.ts: -------------------------------------------------------------------------------- 1 | import { ActionMetadata } from './ActionMetadata'; 2 | import { ControllerMetadataArgs } from './args/ControllerMetadataArgs'; 3 | import { UseMetadata } from './UseMetadata'; 4 | import { getFromContainer } from '../container'; 5 | import { ControllerOptions } from '../decorator-options/ControllerOptions'; 6 | import { ResponseHandlerMetadata } from './ResponseHandleMetadata'; 7 | import { InterceptorMetadata } from './InterceptorMetadata'; 8 | import { Action } from '../Action'; 9 | 10 | /** 11 | * Controller metadata. 12 | */ 13 | export class ControllerMetadata { 14 | // ------------------------------------------------------------------------- 15 | // Properties 16 | // ------------------------------------------------------------------------- 17 | 18 | /** 19 | * Controller actions. 20 | */ 21 | actions: ActionMetadata[]; 22 | 23 | /** 24 | * Indicates object which is used by this controller. 25 | */ 26 | target: Function; 27 | 28 | /** 29 | * Base route for all actions registered in this controller. 30 | */ 31 | route: string; 32 | 33 | /** 34 | * Controller type. Can be default or json-typed. Json-typed controllers operate with json requests and responses. 35 | */ 36 | type: 'default' | 'json'; 37 | 38 | /** 39 | * Options that apply to all controller actions. 40 | */ 41 | options: ControllerOptions; 42 | 43 | /** 44 | * Middleware "use"-s applied to a whole controller. 45 | */ 46 | uses: UseMetadata[]; 47 | 48 | /** 49 | * Middleware "use"-s applied to a whole controller. 50 | */ 51 | interceptors: InterceptorMetadata[]; 52 | 53 | /** 54 | * Indicates if this action uses Authorized decorator. 55 | */ 56 | isAuthorizedUsed: boolean; 57 | 58 | /** 59 | * Roles set by @Authorized decorator. 60 | */ 61 | authorizedRoles: any[]; 62 | 63 | // ------------------------------------------------------------------------- 64 | // Constructor 65 | // ------------------------------------------------------------------------- 66 | 67 | constructor(args: ControllerMetadataArgs) { 68 | this.target = args.target; 69 | this.route = args.route; 70 | this.type = args.type; 71 | this.options = args.options; 72 | } 73 | 74 | // ------------------------------------------------------------------------- 75 | // Accessors 76 | // ------------------------------------------------------------------------- 77 | 78 | /** 79 | * Gets instance of the controller. 80 | * @param action Details around the request session 81 | */ 82 | getInstance(action: Action): any { 83 | return getFromContainer(this.target, action); 84 | } 85 | 86 | // ------------------------------------------------------------------------- 87 | // Public Methods 88 | // ------------------------------------------------------------------------- 89 | 90 | /** 91 | * Builds everything controller metadata needs. 92 | * Controller metadata should be used only after its build. 93 | */ 94 | build(responseHandlers: ResponseHandlerMetadata[]) { 95 | const authorizedHandler = responseHandlers.find(handler => handler.type === 'authorized' && !handler.method); 96 | this.isAuthorizedUsed = !!authorizedHandler; 97 | this.authorizedRoles = [].concat((authorizedHandler && authorizedHandler.value) || []); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/metadata/InterceptorMetadata.ts: -------------------------------------------------------------------------------- 1 | import { UseInterceptorMetadataArgs } from './args/UseInterceptorMetadataArgs'; 2 | 3 | /** 4 | * "Use interceptor" metadata. 5 | */ 6 | export class InterceptorMetadata { 7 | // ------------------------------------------------------------------------- 8 | // Properties 9 | // ------------------------------------------------------------------------- 10 | 11 | /** 12 | * Object class of the interceptor class. 13 | */ 14 | target: Function; 15 | 16 | /** 17 | * Method used by this "use". 18 | */ 19 | method: string; 20 | 21 | /** 22 | * Interceptor class or function to be executed by this "use". 23 | */ 24 | interceptor: Function; 25 | 26 | /** 27 | * Indicates if this interceptor is global or not. 28 | */ 29 | global: boolean; 30 | 31 | /** 32 | * Interceptor priority. Used for global interceptors. 33 | */ 34 | priority: number; 35 | 36 | // ------------------------------------------------------------------------- 37 | // Constructor 38 | // ------------------------------------------------------------------------- 39 | 40 | constructor(args: UseInterceptorMetadataArgs) { 41 | this.target = args.target; 42 | this.method = args.method; 43 | this.interceptor = args.interceptor; 44 | this.priority = args.priority; 45 | this.global = args.global; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/metadata/MiddlewareMetadata.ts: -------------------------------------------------------------------------------- 1 | import { MiddlewareMetadataArgs } from './args/MiddlewareMetadataArgs'; 2 | import { ExpressMiddlewareInterface } from '../driver/express/ExpressMiddlewareInterface'; 3 | import { ExpressErrorMiddlewareInterface } from '../driver/express/ExpressErrorMiddlewareInterface'; 4 | import { getFromContainer } from '../container'; 5 | import { KoaMiddlewareInterface } from '../driver/koa/KoaMiddlewareInterface'; 6 | 7 | /** 8 | * Middleware metadata. 9 | */ 10 | export class MiddlewareMetadata { 11 | // ------------------------------------------------------------------------- 12 | // Properties 13 | // ------------------------------------------------------------------------- 14 | 15 | /** 16 | * Indicates if this middleware is global, thous applied to all routes. 17 | */ 18 | global: boolean; 19 | 20 | /** 21 | * Object class of the middleware class. 22 | */ 23 | target: Function; 24 | 25 | /** 26 | * Execution priority of the middleware. 27 | */ 28 | priority: number; 29 | 30 | /** 31 | * Indicates if middleware must be executed after routing action is executed. 32 | */ 33 | type: 'before' | 'after'; 34 | 35 | // ------------------------------------------------------------------------- 36 | // Constructor 37 | // ------------------------------------------------------------------------- 38 | 39 | constructor(args: MiddlewareMetadataArgs) { 40 | this.global = args.global; 41 | this.target = args.target; 42 | this.priority = args.priority; 43 | this.type = args.type; 44 | } 45 | 46 | // ------------------------------------------------------------------------- 47 | // Accessors 48 | // ------------------------------------------------------------------------- 49 | 50 | /** 51 | * Gets middleware instance from the container. 52 | */ 53 | get instance(): ExpressMiddlewareInterface | KoaMiddlewareInterface | ExpressErrorMiddlewareInterface { 54 | return getFromContainer( 55 | this.target 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/metadata/ParamMetadata.ts: -------------------------------------------------------------------------------- 1 | import { ValidatorOptions } from 'class-validator'; 2 | import { ActionMetadata } from './ActionMetadata'; 3 | import { ParamMetadataArgs } from './args/ParamMetadataArgs'; 4 | import { ParamType } from './types/ParamType'; 5 | import { ClassTransformOptions } from 'class-transformer'; 6 | import { Action } from '../Action'; 7 | 8 | /** 9 | * Action Parameter metadata. 10 | */ 11 | export class ParamMetadata { 12 | // ------------------------------------------------------------------------- 13 | // Properties 14 | // ------------------------------------------------------------------------- 15 | 16 | /** 17 | * Parameter's action. 18 | */ 19 | actionMetadata: ActionMetadata; 20 | 21 | /** 22 | * Object on which's method's parameter this parameter is attached. 23 | */ 24 | object: any; 25 | 26 | /** 27 | * Method on which's parameter is attached. 28 | */ 29 | method: string; 30 | 31 | /** 32 | * Index (# number) of the parameter in the method signature. 33 | */ 34 | index: number; 35 | 36 | /** 37 | * Parameter type. 38 | */ 39 | type: ParamType; 40 | 41 | /** 42 | * Parameter name. 43 | */ 44 | name: string; 45 | 46 | /** 47 | * Parameter target type. 48 | */ 49 | targetType?: any; 50 | 51 | /** 52 | * Parameter target type's name in lowercase. 53 | */ 54 | targetName: string = ''; 55 | 56 | /** 57 | * Indicates if target type is an object. 58 | */ 59 | isTargetObject: boolean = false; 60 | 61 | /** 62 | * Parameter target. 63 | */ 64 | target: any; 65 | 66 | /** 67 | * Specifies if parameter should be parsed as json or not. 68 | */ 69 | parse: boolean; 70 | 71 | /** 72 | * Indicates if this parameter is required or not 73 | */ 74 | required: boolean; 75 | 76 | /** 77 | * Transforms the value. 78 | */ 79 | transform: (action: Action, value?: any) => Promise | any; 80 | 81 | /** 82 | * If true, string values are cast to arrays 83 | */ 84 | isArray?: boolean; 85 | 86 | /** 87 | * Additional parameter options. 88 | * For example it can be uploader middleware options or body-parser middleware options. 89 | */ 90 | extraOptions: any; 91 | 92 | /** 93 | * Class transform options used to perform plainToClass operation. 94 | */ 95 | classTransform?: ClassTransformOptions; 96 | 97 | /** 98 | * If true, class-validator will be used to validate param object. 99 | * If validation options are given then it means validation will be applied (is true). 100 | */ 101 | validate?: boolean | ValidatorOptions; 102 | 103 | // ------------------------------------------------------------------------- 104 | // Constructor 105 | // ------------------------------------------------------------------------- 106 | 107 | constructor(actionMetadata: ActionMetadata, args: ParamMetadataArgs) { 108 | this.actionMetadata = actionMetadata; 109 | 110 | this.target = args.object.constructor; 111 | this.method = args.method; 112 | this.extraOptions = args.extraOptions; 113 | this.index = args.index; 114 | this.type = args.type; 115 | this.name = args.name; 116 | this.parse = args.parse; 117 | this.required = args.required; 118 | this.transform = args.transform; 119 | this.classTransform = args.classTransform; 120 | this.validate = args.validate; 121 | this.isArray = args.isArray; 122 | 123 | if (args.explicitType) { 124 | this.targetType = args.explicitType; 125 | } else { 126 | const ParamTypes = (Reflect as any).getMetadata('design:paramtypes', args.object, args.method); 127 | if (typeof ParamTypes !== 'undefined') { 128 | this.targetType = ParamTypes[args.index]; 129 | } 130 | } 131 | 132 | if (this.targetType) { 133 | if (this.targetType instanceof Function && this.targetType.name) { 134 | this.targetName = this.targetType.name.toLowerCase(); 135 | } else if (typeof this.targetType === 'string') { 136 | this.targetName = this.targetType.toLowerCase(); 137 | } 138 | this.isTargetObject = this.targetType instanceof Function || this.targetType.toLowerCase() === 'object'; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/metadata/ResponseHandleMetadata.ts: -------------------------------------------------------------------------------- 1 | import { ResponseHandlerMetadataArgs } from './args/ResponseHandleMetadataArgs'; 2 | import { ResponseHandlerType } from './types/ResponseHandlerType'; 3 | 4 | /** 5 | * Response handler metadata. 6 | */ 7 | export class ResponseHandlerMetadata { 8 | // ------------------------------------------------------------------------- 9 | // Properties 10 | // ------------------------------------------------------------------------- 11 | 12 | /** 13 | * Class on which's method decorator is set. 14 | */ 15 | target: Function; 16 | 17 | /** 18 | * Method on which decorator is set. 19 | */ 20 | method: string; 21 | 22 | /** 23 | * Property type. See ResponsePropertyMetadataType for possible values. 24 | */ 25 | type: ResponseHandlerType; 26 | 27 | /** 28 | * Property value. Can be status code, content-type, header name, template name, etc. 29 | */ 30 | value: any; 31 | 32 | /** 33 | * Secondary property value. Can be header value for example. 34 | */ 35 | secondaryValue: any; 36 | 37 | // ------------------------------------------------------------------------- 38 | // Constructor 39 | // ------------------------------------------------------------------------- 40 | 41 | constructor(args: ResponseHandlerMetadataArgs) { 42 | this.target = args.target; 43 | this.method = args.method; 44 | this.type = args.type; 45 | this.value = args.value; 46 | this.secondaryValue = args.secondaryValue; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/metadata/UseMetadata.ts: -------------------------------------------------------------------------------- 1 | import { UseMetadataArgs } from './args/UseMetadataArgs'; 2 | 3 | /** 4 | * "Use middleware" metadata. 5 | */ 6 | export class UseMetadata { 7 | // ------------------------------------------------------------------------- 8 | // Properties 9 | // ------------------------------------------------------------------------- 10 | 11 | /** 12 | * Object class of the middleware class. 13 | */ 14 | target: Function; 15 | 16 | /** 17 | * Method used by this "use". 18 | */ 19 | method: string; 20 | 21 | /** 22 | * Middleware to be executed by this "use". 23 | */ 24 | middleware: Function; 25 | 26 | /** 27 | * Indicates if middleware must be executed after routing action is executed. 28 | */ 29 | afterAction: boolean; 30 | 31 | // ------------------------------------------------------------------------- 32 | // Constructor 33 | // ------------------------------------------------------------------------- 34 | 35 | constructor(args: UseMetadataArgs) { 36 | this.target = args.target; 37 | this.method = args.method; 38 | this.middleware = args.middleware; 39 | this.afterAction = args.afterAction; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/metadata/args/ActionMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | import { ActionType } from '../types/ActionType'; 2 | import { Action } from '../../Action'; 3 | import { ActionMetadata } from '../ActionMetadata'; 4 | import { HandlerOptions } from '../../decorator-options/HandlerOptions'; 5 | 6 | /** 7 | * Action metadata used to storage information about registered action. 8 | */ 9 | export interface ActionMetadataArgs { 10 | /** 11 | * Route to be registered for the action. 12 | */ 13 | route: string | RegExp; 14 | 15 | /** 16 | * Class on which's method this action is attached. 17 | */ 18 | target: Function; 19 | 20 | /** 21 | * Object's method that will be executed on this action. 22 | */ 23 | method: string; 24 | 25 | /** 26 | * Action-specific options. 27 | */ 28 | options: HandlerOptions; 29 | 30 | /** 31 | * Action type represents http method used for the registered route. Can be one of the value defined in ActionTypes 32 | * class. 33 | */ 34 | type: ActionType; 35 | 36 | /** 37 | * Params to be appended to the method call. 38 | */ 39 | appendParams?: (action: Action) => any[]; 40 | 41 | /** 42 | * Special function that will be called instead of orignal method of the target. 43 | */ 44 | methodOverride?: (actionMetadata: ActionMetadata, action: Action, params: any[]) => Promise | any; 45 | } 46 | -------------------------------------------------------------------------------- /src/metadata/args/ControllerMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | import { ControllerOptions } from '../../decorator-options/ControllerOptions'; 2 | 3 | /** 4 | * Controller metadata used to storage information about registered controller. 5 | */ 6 | export interface ControllerMetadataArgs { 7 | /** 8 | * Indicates object which is used by this controller. 9 | */ 10 | target: Function; 11 | 12 | /** 13 | * Base route for all actions registered in this controller. 14 | */ 15 | route: string; 16 | 17 | /** 18 | * Controller type. Can be default or json-typed. Json-typed controllers operate with json requests and responses. 19 | */ 20 | type: 'default' | 'json'; 21 | 22 | /** 23 | * Options that apply to all controller actions. 24 | */ 25 | options: ControllerOptions; 26 | } 27 | -------------------------------------------------------------------------------- /src/metadata/args/ErrorHandlerMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Metadata used to store registered error handlers. 3 | */ 4 | export interface ErrorHandlerMetadataArgs { 5 | /** 6 | * Object class of the error handler class. 7 | */ 8 | target: Function; 9 | 10 | /** 11 | * Execution priority of the error handler. 12 | */ 13 | priority: number; 14 | } 15 | -------------------------------------------------------------------------------- /src/metadata/args/InterceptorMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Metadata used to store registered interceptor. 3 | */ 4 | export interface InterceptorMetadataArgs { 5 | /** 6 | * Object class of the interceptor class. 7 | */ 8 | target: Function; 9 | 10 | /** 11 | * Indicates if this interceptor is global, thous applied to all routes. 12 | */ 13 | global: boolean; 14 | 15 | /** 16 | * Execution priority of the interceptor. 17 | */ 18 | priority: number; 19 | } 20 | -------------------------------------------------------------------------------- /src/metadata/args/MiddlewareMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Metadata used to store registered middlewares. 3 | */ 4 | export interface MiddlewareMetadataArgs { 5 | /** 6 | * Object class of the middleware class. 7 | */ 8 | target: Function; 9 | 10 | /** 11 | * Indicates if this middleware is global, thous applied to all routes. 12 | */ 13 | global: boolean; 14 | 15 | /** 16 | * Execution priority of the middleware. 17 | */ 18 | priority: number; 19 | 20 | /** 21 | * Indicates if middleware must be executed after routing action is executed. 22 | */ 23 | type: 'before' | 'after'; 24 | } 25 | -------------------------------------------------------------------------------- /src/metadata/args/ParamMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | import { ValidatorOptions } from 'class-validator'; 2 | import { ClassTransformOptions } from 'class-transformer'; 3 | import { ParamType } from '../types/ParamType'; 4 | 5 | /** 6 | * Controller metadata used to storage information about registered parameters. 7 | */ 8 | export interface ParamMetadataArgs { 9 | /** 10 | * Parameter object. 11 | */ 12 | object: any; 13 | 14 | /** 15 | * Method on which's parameter is attached. 16 | */ 17 | method: string; 18 | 19 | /** 20 | * Index (# number) of the parameter in the method signature. 21 | */ 22 | index: number; 23 | 24 | /** 25 | * Parameter type. 26 | */ 27 | type: ParamType; 28 | 29 | /** 30 | * Parameter name. 31 | */ 32 | name?: string; 33 | 34 | /** 35 | * Specifies if parameter should be parsed as json or not. 36 | */ 37 | parse: boolean; 38 | 39 | /** 40 | * Indicates if this parameter is required or not 41 | */ 42 | required: boolean; 43 | 44 | /** 45 | * Transforms the value. 46 | */ 47 | transform?: (value?: any, request?: any, response?: any) => Promise | any; 48 | 49 | /** 50 | * Extra parameter options. 51 | */ 52 | extraOptions?: any; 53 | 54 | /** 55 | * Class transform options used to perform plainToClass operation. 56 | */ 57 | classTransform?: ClassTransformOptions; 58 | 59 | /** 60 | * If true, class-validator will be used to validate param object. 61 | * If validation options are given then it means validation will be applied (is true). 62 | */ 63 | validate?: boolean | ValidatorOptions; 64 | 65 | /** 66 | * Explicitly set type which should be used for Body to perform transformation. 67 | */ 68 | explicitType?: any; 69 | 70 | /** 71 | * Explicitly tell that the QueryParam is an array to force routing-controller to cast it 72 | */ 73 | isArray?: boolean; 74 | } 75 | -------------------------------------------------------------------------------- /src/metadata/args/ResponseHandleMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | import { ResponseHandlerType } from '../types/ResponseHandlerType'; 2 | 3 | /** 4 | * Storages information about registered response handlers. 5 | */ 6 | export interface ResponseHandlerMetadataArgs { 7 | /** 8 | * Class on which's method decorator is set. 9 | */ 10 | target: Function; 11 | 12 | /** 13 | * Method on which decorator is set. 14 | */ 15 | method: string; 16 | 17 | /** 18 | * Property type. See ResponsePropertyMetadataType for possible values. 19 | */ 20 | type: ResponseHandlerType; 21 | 22 | /** 23 | * Property value. Can be status code, content-type, header name, template name, etc. 24 | */ 25 | value?: any; 26 | 27 | /** 28 | * Secondary property value. Can be header value for example. 29 | */ 30 | secondaryValue?: any; 31 | } 32 | -------------------------------------------------------------------------------- /src/metadata/args/UseInterceptorMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Metadata used to store registered intercept for a specific controller or controller action. 3 | */ 4 | export interface UseInterceptorMetadataArgs { 5 | /** 6 | * Controller class where this intercept was used. 7 | */ 8 | target: Function; 9 | 10 | /** 11 | * Controller method to which this intercept is applied. 12 | * If method is not given it means intercept is used on the controller. 13 | * Then intercept is applied to all controller's actions. 14 | */ 15 | method?: string; 16 | 17 | /** 18 | * Interceptor class or a function to be executed. 19 | */ 20 | interceptor: Function; 21 | 22 | /** 23 | * Indicates if this interceptor is global, thous applied to all routes. 24 | */ 25 | global?: boolean; 26 | 27 | /** 28 | * Execution priority of the interceptor. 29 | */ 30 | priority?: number; 31 | } 32 | -------------------------------------------------------------------------------- /src/metadata/args/UseMetadataArgs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Metadata used to store registered middlewares. 3 | */ 4 | export interface UseMetadataArgs { 5 | /** 6 | * Object class of this "use". 7 | */ 8 | target: Function; 9 | 10 | /** 11 | * Method to which this "use" is applied. 12 | * If method is not given it means "use" is used on the controller. Then "use" applied to all controller's actions. 13 | */ 14 | method?: string; 15 | 16 | /** 17 | * Middleware to be executed for this "use". 18 | */ 19 | middleware: Function; 20 | 21 | /** 22 | * Indicates if middleware must be executed after routing action is executed. 23 | */ 24 | afterAction: boolean; 25 | } 26 | -------------------------------------------------------------------------------- /src/metadata/types/ActionType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Controller action type. 3 | */ 4 | export type ActionType = 5 | | 'all' 6 | | 'checkout' 7 | | 'connect' 8 | | 'copy' 9 | | 'delete' 10 | | 'get' 11 | | 'head' 12 | | 'lock' 13 | | 'merge' 14 | | 'mkactivity' 15 | | 'mkcol' 16 | | 'move' 17 | | 'm-search' 18 | | 'notify' 19 | | 'options' 20 | | 'patch' 21 | | 'post' 22 | | 'propfind' 23 | | 'proppatch' 24 | | 'purge' 25 | | 'put' 26 | | 'report' 27 | | 'search' 28 | | 'subscribe' 29 | | 'trace' 30 | | 'unlock' 31 | | 'unsubscribe'; 32 | -------------------------------------------------------------------------------- /src/metadata/types/ParamType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Controller action's parameter type. 3 | */ 4 | export type ParamType = 5 | | 'body' 6 | | 'body-param' 7 | | 'query' 8 | | 'queries' 9 | | 'header' 10 | | 'headers' 11 | | 'file' 12 | | 'files' 13 | | 'param' 14 | | 'params' 15 | | 'session' 16 | | 'session-param' 17 | | 'state' 18 | | 'cookie' 19 | | 'cookies' 20 | | 'request' 21 | | 'response' 22 | | 'context' 23 | | 'current-user' 24 | | 'custom-converter'; 25 | -------------------------------------------------------------------------------- /src/metadata/types/ResponseHandlerType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Response handler type. 3 | */ 4 | export type ResponseHandlerType = 5 | | 'success-code' 6 | | 'error-code' 7 | | 'content-type' 8 | | 'header' 9 | | 'rendered-template' 10 | | 'redirect' 11 | | 'location' 12 | | 'on-null' 13 | | 'on-undefined' 14 | | 'response-class-transform-options' 15 | | 'authorized'; 16 | -------------------------------------------------------------------------------- /src/util/container.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Container options. 3 | */ 4 | export interface UseContainerOptions { 5 | /** 6 | * If set to true, then default container will be used in the case if given container haven't returned anything. 7 | */ 8 | fallback?: boolean; 9 | 10 | /** 11 | * If set to true, then default container will be used in the case if given container thrown an exception. 12 | */ 13 | fallbackOnErrors?: boolean; 14 | } 15 | 16 | /** 17 | * Container to be used by this library for inversion control. If container was not implicitly set then by default 18 | * container simply creates a new instance of the given class. 19 | */ 20 | const defaultContainer: { get(someClass: { new (...args: any[]): T } | Function): T } = new (class { 21 | private instances: { type: Function; object: any }[] = []; 22 | get(someClass: { new (...args: any[]): T }): T { 23 | let instance = this.instances.find(instance => instance.type === someClass); 24 | if (!instance) { 25 | instance = { type: someClass, object: new someClass() }; 26 | this.instances.push(instance); 27 | } 28 | 29 | return instance.object; 30 | } 31 | })(); 32 | 33 | let userContainer: { get(someClass: { new (...args: any[]): T } | Function): T }; 34 | let userContainerOptions: UseContainerOptions; 35 | 36 | /** 37 | * Sets container to be used by this library. 38 | */ 39 | export function useContainer(iocContainer: { get(someClass: any): any }, options?: UseContainerOptions): void { 40 | userContainer = iocContainer; 41 | userContainerOptions = options; 42 | } 43 | 44 | /** 45 | * Gets the IOC container used by this library. 46 | */ 47 | export function getFromContainer(someClass: { new (...args: any[]): T } | Function): T { 48 | if (userContainer) { 49 | try { 50 | const instance = userContainer.get(someClass); 51 | if (instance) return instance; 52 | 53 | if (!userContainerOptions || !userContainerOptions.fallback) return instance; 54 | } catch (error) { 55 | if (!userContainerOptions || !userContainerOptions.fallbackOnErrors) throw error; 56 | } 57 | } 58 | return defaultContainer.get(someClass); 59 | } 60 | -------------------------------------------------------------------------------- /src/util/importClassesFromDirectories.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | /** 4 | * Loads all exported classes from the given directory. 5 | */ 6 | export function importClassesFromDirectories(directories: string[], formats = ['.js', '.ts', '.tsx']): Function[] { 7 | const loadFileClasses = function (exported: any, allLoaded: Function[]) { 8 | if (exported instanceof Function) { 9 | allLoaded.push(exported); 10 | } else if (exported instanceof Array) { 11 | exported.forEach((i: any) => loadFileClasses(i, allLoaded)); 12 | } else if (exported instanceof Object || typeof exported === 'object') { 13 | Object.keys(exported).forEach(key => loadFileClasses(exported[key], allLoaded)); 14 | } 15 | 16 | return allLoaded; 17 | }; 18 | 19 | const allFiles = directories.reduce((allDirs, dir) => { 20 | // Replace \ with / for glob 21 | return allDirs.concat(require('glob').sync(path.normalize(dir).replace(/\\/g, '/'))); 22 | }, [] as string[]); 23 | 24 | const dirs = allFiles 25 | .filter(file => { 26 | const dtsExtension = file.substring(file.length - 5, file.length); 27 | return formats.indexOf(path.extname(file)) !== -1 && dtsExtension !== '.d.ts'; 28 | }) 29 | .map(file => { 30 | return require(file); 31 | }); 32 | 33 | return loadFileClasses(dirs, []); 34 | } 35 | -------------------------------------------------------------------------------- /src/util/isPromiseLike.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if given value is a Promise-like object. 3 | */ 4 | export function isPromiseLike(arg: any): arg is Promise { 5 | return arg != null && typeof arg === 'object' && typeof arg.then === 'function'; 6 | } 7 | -------------------------------------------------------------------------------- /src/util/runInSequence.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Runs given callback that returns promise for each item in the given collection in order. 3 | * Operations executed after each other, right after previous promise being resolved. 4 | */ 5 | export function runInSequence(collection: T[], callback: (item: T) => Promise): Promise { 6 | const results: U[] = []; 7 | return collection 8 | .reduce((promise, item) => { 9 | return promise 10 | .then(() => { 11 | return callback(item); 12 | }) 13 | .then(result => { 14 | results.push(result); 15 | }); 16 | }, Promise.resolve()) 17 | .then(() => { 18 | return results; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /test/fakes/global-options/FakeService.ts: -------------------------------------------------------------------------------- 1 | export class FakeService { 2 | fileMiddlewareCalled = false; 3 | videoMiddlewareCalled = false; 4 | questionMiddlewareCalled = false; 5 | questionErrorMiddlewareCalled = false; 6 | postMiddlewareCalled = false; 7 | 8 | fileMiddleware() { 9 | this.fileMiddlewareCalled = true; 10 | console.log('fake service!'); 11 | } 12 | 13 | videoMiddleware() { 14 | this.videoMiddlewareCalled = true; 15 | console.log('fake service!'); 16 | } 17 | 18 | questionMiddleware() { 19 | this.questionMiddlewareCalled = true; 20 | console.log('fake service!'); 21 | } 22 | 23 | questionErrorMiddleware() { 24 | this.questionErrorMiddlewareCalled = true; 25 | console.log('fake service!'); 26 | } 27 | 28 | postMiddleware() { 29 | this.postMiddlewareCalled = true; 30 | console.log('fake service!'); 31 | } 32 | 33 | reset() { 34 | this.fileMiddlewareCalled = false; 35 | this.videoMiddlewareCalled = false; 36 | this.questionMiddlewareCalled = false; 37 | this.questionErrorMiddlewareCalled = false; 38 | this.postMiddlewareCalled = false; 39 | } 40 | } 41 | 42 | export const defaultFakeService = new FakeService(); 43 | -------------------------------------------------------------------------------- /test/fakes/global-options/SessionMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../../src/driver/express/ExpressMiddlewareInterface'; 2 | import session from 'express-session'; 3 | 4 | const convert = require('koa-convert'); 5 | const KoaSession = require('koa-session'); 6 | 7 | export class SessionMiddleware implements ExpressMiddlewareInterface { 8 | public use(requestOrContext: any, responseOrNext: any, next?: (err?: any) => any): any { 9 | if (next) { 10 | return this.expSession(requestOrContext, responseOrNext, next); 11 | } else { 12 | if (!this.koaSession) { 13 | this.koaSession = convert(KoaSession(requestOrContext.app)); 14 | } 15 | return this.koaSession(requestOrContext, responseOrNext); 16 | } 17 | } 18 | 19 | private expSession = (session as any)({ 20 | secret: '19majkel94_helps_pleerock', 21 | resave: false, 22 | saveUninitialized: true, 23 | }); 24 | 25 | private koaSession: any; 26 | } 27 | -------------------------------------------------------------------------------- /test/fakes/global-options/User.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | public username: string; 3 | public location: string; 4 | public twitter: string; 5 | } 6 | -------------------------------------------------------------------------------- /test/fakes/global-options/express-middlewares/post/PostMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../../../../src/driver/express/ExpressMiddlewareInterface'; 2 | import { defaultFakeService } from '../../FakeService'; 3 | import { Middleware } from '../../../../../src/decorator/Middleware'; 4 | 5 | @Middleware({ type: 'before' }) 6 | export class PostMiddleware implements ExpressMiddlewareInterface { 7 | use(request: any, response: any, next?: (err?: any) => any): any { 8 | defaultFakeService.postMiddleware(); 9 | next(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fakes/global-options/express-middlewares/question/QuestionErrorHandler.ts: -------------------------------------------------------------------------------- 1 | import { ExpressErrorMiddlewareInterface } from '../../../../../src/driver/express/ExpressErrorMiddlewareInterface'; 2 | import { defaultFakeService } from '../../FakeService'; 3 | import { Middleware } from '../../../../../src/decorator/Middleware'; 4 | 5 | @Middleware({ type: 'after' }) 6 | export class QuestionErrorHandler implements ExpressErrorMiddlewareInterface { 7 | error(error: any, request: any, response: any, next?: (err?: any) => any): any { 8 | defaultFakeService.questionErrorMiddleware(); 9 | next(error); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fakes/global-options/express-middlewares/question/QuestionMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../../../../src/driver/express/ExpressMiddlewareInterface'; 2 | import { defaultFakeService } from '../../FakeService'; 3 | import { Middleware } from '../../../../../src/decorator/Middleware'; 4 | 5 | @Middleware({ type: 'before' }) 6 | export class QuestionMiddleware implements ExpressMiddlewareInterface { 7 | use(request: any, response: any, next?: (err?: any) => any): any { 8 | defaultFakeService.questionMiddleware(); 9 | return next(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fakes/global-options/first-controllers/post/PostController.ts: -------------------------------------------------------------------------------- 1 | import { JsonController } from '../../../../../src/decorator/JsonController'; 2 | import { Get } from '../../../../../src/decorator/Get'; 3 | 4 | @JsonController() 5 | export class PostController { 6 | @Get('/posts') 7 | getAll() { 8 | return [ 9 | { 10 | id: 1, 11 | title: '#1', 12 | }, 13 | { 14 | id: 2, 15 | title: '#2', 16 | }, 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/fakes/global-options/first-controllers/question/AnswerController.ts: -------------------------------------------------------------------------------- 1 | import { JsonController } from '../../../../../src/decorator/JsonController'; 2 | import { Get } from '../../../../../src/decorator/Get'; 3 | 4 | @JsonController() 5 | export class AnswerController { 6 | @Get('/answers') 7 | getAll() { 8 | return [ 9 | { 10 | id: 1, 11 | title: '#1', 12 | }, 13 | { 14 | id: 2, 15 | title: '#2', 16 | }, 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/fakes/global-options/first-controllers/question/QuestionController.ts: -------------------------------------------------------------------------------- 1 | import { JsonController } from '../../../../../src/decorator/JsonController'; 2 | import { Get } from '../../../../../src/decorator/Get'; 3 | 4 | @JsonController() 5 | export class QuestionController { 6 | @Get('/questions') 7 | getAll() { 8 | return [ 9 | { 10 | id: 1, 11 | title: '#1', 12 | }, 13 | { 14 | id: 2, 15 | title: '#2', 16 | }, 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/fakes/global-options/koa-middlewares/FileMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../../../src/driver/express/ExpressMiddlewareInterface'; 2 | import { defaultFakeService } from '../FakeService'; 3 | import { Middleware } from '../../../../src/decorator/Middleware'; 4 | 5 | @Middleware({ type: 'before' }) 6 | export class FileMiddleware implements ExpressMiddlewareInterface { 7 | use(context: any, next?: (err?: any) => Promise): Promise { 8 | defaultFakeService.fileMiddleware(); 9 | return next(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fakes/global-options/koa-middlewares/SetStateMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../../../src/driver/express/ExpressMiddlewareInterface'; 2 | import { User } from '../User'; 3 | 4 | export class SetStateMiddleware implements ExpressMiddlewareInterface { 5 | public use(context: any, next: (err?: any) => Promise): Promise { 6 | const user = new User(); 7 | user.username = 'pleerock'; 8 | user.location = 'Dushanbe, Tajikistan'; 9 | user.twitter = 'https://twitter.com/pleerock'; 10 | context.state = user; 11 | return next(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/fakes/global-options/koa-middlewares/VideoMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ExpressMiddlewareInterface } from '../../../../src/driver/express/ExpressMiddlewareInterface'; 2 | import { defaultFakeService } from '../FakeService'; 3 | import { Middleware } from '../../../../src/decorator/Middleware'; 4 | 5 | @Middleware({ type: 'before' }) 6 | export class VideoMiddleware implements ExpressMiddlewareInterface { 7 | use(context: any, next?: (err?: any) => Promise): Promise { 8 | defaultFakeService.videoMiddleware(); 9 | return next(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fakes/global-options/second-controllers/PhotoController.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '../../../../src/decorator/Controller'; 2 | import { Get } from '../../../../src/decorator/Get'; 3 | 4 | @Controller() 5 | export class PhotoController { 6 | @Get('/photos') 7 | getAll() { 8 | return 'Hello photos'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fakes/global-options/second-controllers/VideoController.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '../../../../src/decorator/Controller'; 2 | import { Get } from '../../../../src/decorator/Get'; 3 | 4 | @Controller() 5 | export class VideoController { 6 | @Get('/videos') 7 | getAll() { 8 | return 'Hello videos'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/functional/action-options.spec.ts: -------------------------------------------------------------------------------- 1 | import { Exclude, Expose } from 'class-transformer'; 2 | import { defaultMetadataStorage } from 'class-transformer/cjs/storage'; 3 | import { Body } from '../../src/decorator/Body'; 4 | import { JsonController } from '../../src/decorator/JsonController'; 5 | import { Post } from '../../src/decorator/Post'; 6 | import { createExpressServer, getMetadataArgsStorage } from '../../src/index'; 7 | import { axios } from '../utilities/axios'; 8 | 9 | describe(``, () => { 10 | let expressApp: any; 11 | let initializedUser: any; 12 | let user: any = { firstName: 'Umed', lastName: 'Khudoiberdiev' }; 13 | 14 | @Exclude() 15 | class UserModel { 16 | @Expose() 17 | firstName: string; 18 | 19 | lastName: string; 20 | } 21 | 22 | beforeAll(done => { 23 | // reset metadata args storage 24 | getMetadataArgsStorage().reset(); 25 | 26 | function handler(user: UserModel) { 27 | initializedUser = user; 28 | const ret = new UserModel(); 29 | ret.firstName = user.firstName; 30 | ret.lastName = user.lastName || 'default'; 31 | return ret; 32 | } 33 | 34 | @JsonController('', { transformResponse: false }) 35 | class NoTransformResponseController { 36 | @Post('/default') 37 | default(@Body() user: UserModel) { 38 | return handler(user); 39 | } 40 | 41 | @Post('/transformRequestOnly', { transformRequest: true, transformResponse: false }) 42 | transformRequestOnly(@Body() user: UserModel) { 43 | return handler(user); 44 | } 45 | 46 | @Post('/transformResponseOnly', { transformRequest: false, transformResponse: true }) 47 | transformResponseOnly(@Body() user: UserModel) { 48 | return handler(user); 49 | } 50 | } 51 | 52 | expressApp = createExpressServer().listen(3001, done); 53 | }); 54 | 55 | afterAll(done => { 56 | defaultMetadataStorage.clear(); 57 | expressApp.close(done); 58 | }); 59 | 60 | beforeEach(() => { 61 | initializedUser = undefined; 62 | }); 63 | 64 | it('should use controller options when action transform options are not set', async () => { 65 | expect.assertions(4); 66 | const response = await axios.post('/default', user); 67 | expect(initializedUser).toBeInstanceOf(UserModel); 68 | expect(initializedUser.lastName).toBeUndefined(); 69 | expect(response.status).toBe(200); 70 | expect(response.data.lastName).toBe('default'); 71 | }); 72 | 73 | it('should override controller options with action transformRequest option', async () => { 74 | expect.assertions(4); 75 | const response = await axios.post('/transformRequestOnly', user); 76 | expect(initializedUser).toBeInstanceOf(UserModel); 77 | expect(initializedUser.lastName).toBeUndefined(); 78 | expect(response.status).toBe(200); 79 | expect(response.data.lastName).toBe('default'); 80 | }); 81 | 82 | it('should override controller options with action transformResponse option', async () => { 83 | expect.assertions(4); 84 | const response = await axios.post('/transformResponseOnly', user); 85 | expect(initializedUser).not.toBeInstanceOf(UserModel); 86 | expect(initializedUser.lastName).not.toBeUndefined(); 87 | expect(response.status).toBe(200); 88 | expect(response.data.lastName).toBeUndefined(); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/functional/controller-base-routes.spec.ts: -------------------------------------------------------------------------------- 1 | import { Server as HttpServer } from 'http'; 2 | import HttpStatusCodes from 'http-status-codes'; 3 | import { Controller } from '../../src/decorator/Controller'; 4 | import { Get } from '../../src/decorator/Get'; 5 | import { createExpressServer, getMetadataArgsStorage } from '../../src/index'; 6 | import { axios } from '../utilities/axios'; 7 | import DoneCallback = jest.DoneCallback; 8 | 9 | describe(``, () => { 10 | let expressServer: HttpServer; 11 | 12 | describe('controller > base routes functionality', () => { 13 | beforeEach((done: DoneCallback) => { 14 | getMetadataArgsStorage().reset(); 15 | 16 | @Controller('/posts') 17 | class PostController { 18 | @Get('/') 19 | getAll(): string { 20 | return 'All posts'; 21 | } 22 | 23 | @Get('/:id(\\d+)') 24 | getUserById(): string { 25 | return 'One post'; 26 | } 27 | 28 | @Get(/\/categories\/(\d+)/) 29 | getCategoryById(): string { 30 | return 'One post category'; 31 | } 32 | 33 | @Get('/:postId(\\d+)/users/:userId(\\d+)') 34 | getPostById(): string { 35 | return 'One user'; 36 | } 37 | } 38 | 39 | expressServer = createExpressServer().listen(3001, done); 40 | }); 41 | 42 | afterEach((done: DoneCallback) => { 43 | expressServer.close(done); 44 | }); 45 | 46 | it('get should respond with proper status code, headers and body content', async () => { 47 | expect.assertions(3); 48 | const response = await axios.get('/posts'); 49 | expect(response.status).toEqual(HttpStatusCodes.OK); 50 | expect(response.headers['content-type']).toEqual('text/html; charset=utf-8'); 51 | expect(response.data).toEqual('All posts'); 52 | }); 53 | 54 | it('get should respond with proper status code, headers and body content', async () => { 55 | expect.assertions(3); 56 | const response = await axios.get('/posts/1'); 57 | expect(response.status).toEqual(HttpStatusCodes.OK); 58 | expect(response.headers['content-type']).toEqual('text/html; charset=utf-8'); 59 | expect(response.data).toEqual('One post'); 60 | }); 61 | 62 | it('get should respond with proper status code, headers and body content - 2nd pass', async () => { 63 | expect.assertions(3); 64 | const response = await axios.get('posts/1/users/2'); 65 | expect(response.status).toEqual(HttpStatusCodes.OK); 66 | expect(response.headers['content-type']).toEqual('text/html; charset=utf-8'); 67 | expect(response.data).toEqual('One user'); 68 | }); 69 | 70 | it('wrong route should respond with 404 error', async () => { 71 | expect.assertions(1); 72 | try { 73 | await axios.get('/1/users/1'); 74 | } catch (error) { 75 | expect(error.response.status).toEqual(HttpStatusCodes.NOT_FOUND); 76 | } 77 | }); 78 | 79 | it('wrong route should respond with 404 error', async () => { 80 | expect.assertions(1); 81 | try { 82 | await axios.get('/categories/1'); 83 | } catch (error) { 84 | expect(error.response.status).toEqual(HttpStatusCodes.NOT_FOUND); 85 | } 86 | }); 87 | 88 | it('wrong route should respond with 404 error', async () => { 89 | expect.assertions(1); 90 | try { 91 | await axios.get('/users/1'); 92 | } catch (error) { 93 | expect(error.response.status).toEqual(HttpStatusCodes.NOT_FOUND); 94 | } 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/functional/controller-options.spec.ts: -------------------------------------------------------------------------------- 1 | import { Exclude, Expose } from 'class-transformer'; 2 | import { defaultMetadataStorage } from 'class-transformer/cjs/storage'; 3 | import { Body } from '../../src/decorator/Body'; 4 | import { JsonController } from '../../src/decorator/JsonController'; 5 | import { Post } from '../../src/decorator/Post'; 6 | import { createExpressServer, getMetadataArgsStorage } from '../../src/index'; 7 | import { axios } from '../utilities/axios'; 8 | 9 | describe(``, () => { 10 | let expressServer: any; 11 | 12 | describe('controller options', () => { 13 | let initializedUser: any; 14 | let user: any = { firstName: 'Umed', lastName: 'Khudoiberdiev' }; 15 | 16 | @Exclude() 17 | class UserModel { 18 | @Expose() 19 | firstName: string; 20 | 21 | lastName: string; 22 | } 23 | 24 | beforeAll(done => { 25 | // reset metadata args storage 26 | getMetadataArgsStorage().reset(); 27 | 28 | function handler(user: UserModel) { 29 | initializedUser = user; 30 | const ret = new UserModel(); 31 | ret.firstName = user.firstName; 32 | ret.lastName = user.lastName; 33 | return ret; 34 | } 35 | 36 | @JsonController('/default') 37 | class DefaultController { 38 | @Post('/') 39 | postUsers(@Body() user: UserModel) { 40 | return handler(user); 41 | } 42 | } 43 | 44 | @JsonController('/transform', { transformRequest: true, transformResponse: true }) 45 | class TransformController { 46 | @Post('/') 47 | postUsers(@Body() user: UserModel) { 48 | return handler(user); 49 | } 50 | } 51 | 52 | @JsonController('/noTransform', { transformRequest: false, transformResponse: false }) 53 | class NoTransformController { 54 | @Post('/') 55 | postUsers(@Body() user: UserModel) { 56 | return handler(user); 57 | } 58 | } 59 | 60 | expressServer = createExpressServer().listen(3001, done); 61 | }); 62 | 63 | afterAll(done => { 64 | defaultMetadataStorage.clear(); 65 | expressServer.close(done); 66 | }); 67 | 68 | beforeEach(() => { 69 | initializedUser = undefined; 70 | }); 71 | 72 | it('controller transform is enabled by default', async () => { 73 | expect.assertions(4); 74 | try { 75 | const response = await axios.post('/default', user); 76 | expect(initializedUser).toBeInstanceOf(UserModel); 77 | expect(initializedUser.lastName).toBeUndefined(); 78 | expect(response.status).toBe(200); 79 | expect(response.data.lastName).toBeUndefined(); 80 | } catch (err) { 81 | console.log(err); 82 | } 83 | }); 84 | 85 | it('when controller transform is enabled', async () => { 86 | expect.assertions(4); 87 | try { 88 | const response = await axios.post('/transform', user); 89 | expect(initializedUser).toBeInstanceOf(UserModel); 90 | expect(initializedUser.lastName).toBeUndefined(); 91 | expect(response.status).toBe(200); 92 | expect(response.data.lastName).toBeUndefined(); 93 | } catch (err) { 94 | console.log(err); 95 | } 96 | }); 97 | 98 | it('when controller transform is disabled', async () => { 99 | expect.assertions(4); 100 | try { 101 | const response = await axios.post('/noTransform', user); 102 | expect(initializedUser).toMatchObject(user); 103 | expect(initializedUser.lastName).toBeDefined(); 104 | expect(response.status).toBe(200); 105 | expect(response.data.lastName).toBeDefined(); 106 | } catch (err) { 107 | console.log(err); 108 | } 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/functional/defaults.spec.ts: -------------------------------------------------------------------------------- 1 | import { Server as HttpServer } from 'http'; 2 | import HttpStatusCodes from 'http-status-codes'; 3 | import { Controller } from '../../src/decorator/Controller'; 4 | import { Get } from '../../src/decorator/Get'; 5 | import { OnUndefined } from '../../src/decorator/OnUndefined'; 6 | import { QueryParam } from '../../src/decorator/QueryParam'; 7 | import { createExpressServer, getMetadataArgsStorage } from '../../src/index'; 8 | import { axios } from '../utilities/axios'; 9 | import DoneCallback = jest.DoneCallback; 10 | 11 | describe(``, () => { 12 | let expressServer: HttpServer; 13 | 14 | describe('defaults', () => { 15 | const defaultUndefinedResultCode = 204; 16 | const defaultNullResultCode = 404; 17 | 18 | beforeAll((done: DoneCallback) => { 19 | getMetadataArgsStorage().reset(); 20 | 21 | @Controller() 22 | class ExpressController { 23 | @Get('/voidfunc') 24 | voidFunc(): void { 25 | // Empty 26 | } 27 | 28 | @Get('/promisevoidfunc') 29 | promiseVoidFunc(): Promise { 30 | return Promise.resolve(); 31 | } 32 | 33 | @Get('/paramfunc') 34 | paramFunc(@QueryParam('x') x: number): any { 35 | return { 36 | foo: 'bar', 37 | }; 38 | } 39 | 40 | @Get('/nullfunc') 41 | nullFunc(): null { 42 | return null; 43 | } 44 | 45 | @Get('/overridefunc') 46 | @OnUndefined(HttpStatusCodes.NOT_ACCEPTABLE) 47 | overrideFunc(): void { 48 | // Empty 49 | } 50 | 51 | @Get('/overrideparamfunc') 52 | overrideParamFunc(@QueryParam('x', { required: false }) x: number): any { 53 | return { 54 | foo: 'bar', 55 | }; 56 | } 57 | } 58 | 59 | expressServer = createExpressServer({ 60 | defaults: { 61 | nullResultCode: defaultNullResultCode, 62 | undefinedResultCode: defaultUndefinedResultCode, 63 | paramOptions: { 64 | required: true, 65 | }, 66 | }, 67 | }).listen(3001, done); 68 | }); 69 | 70 | afterAll((done: DoneCallback) => { 71 | expressServer.close(done); 72 | }); 73 | 74 | it('should return undefinedResultCode from defaults config for void function', async () => { 75 | expect.assertions(1); 76 | const response = await axios.get('/voidfunc'); 77 | expect(response.status).toEqual(defaultUndefinedResultCode); 78 | }); 79 | 80 | it('should return undefinedResultCode from defaults config for promise void function', async () => { 81 | expect.assertions(1); 82 | const response = await axios.get('/promisevoidfunc'); 83 | expect(response.status).toEqual(defaultUndefinedResultCode); 84 | }); 85 | 86 | it('should return 400 from required paramOptions', async () => { 87 | expect.assertions(1); 88 | try { 89 | await axios.get('/paramfunc'); 90 | } catch (error) { 91 | expect(error.response.status).toEqual(HttpStatusCodes.BAD_REQUEST); 92 | } 93 | }); 94 | 95 | it('should return nullResultCode from defaults config', async () => { 96 | expect.assertions(1); 97 | try { 98 | await axios.get('/nullfunc'); 99 | } catch (error) { 100 | expect(error.response.status).toEqual(defaultNullResultCode); 101 | } 102 | }); 103 | 104 | it('should return status code from OnUndefined annotation', async () => { 105 | expect.assertions(1); 106 | try { 107 | await axios.get('/overridefunc'); 108 | } catch (error) { 109 | expect(error.response.status).toEqual(HttpStatusCodes.NOT_ACCEPTABLE); 110 | } 111 | }); 112 | 113 | it('should mark arg optional from QueryParam annotation', async () => { 114 | expect.assertions(1); 115 | const response = await axios.get('/overrideparamfunc'); 116 | expect(response.status).toEqual(HttpStatusCodes.OK); 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /test/functional/error-subclasses.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpError } from '../../src/http-error/HttpError'; 2 | import { BadRequestError } from '../../src/http-error/BadRequestError'; 3 | 4 | describe('HttpError', () => { 5 | it('should be instance of HttpError and Error', () => { 6 | const error = new HttpError(418, 'Error message'); 7 | expect(error.httpCode).toEqual(418); 8 | expect(error.message).toEqual('Error message'); 9 | expect(error).toBeInstanceOf(HttpError); 10 | expect(error).toBeInstanceOf(Error); 11 | }); 12 | }); 13 | 14 | describe('BadRequestError', () => { 15 | it('should be instance of BadRequestError, HttpError and Error', () => { 16 | const error = new BadRequestError('Error message'); 17 | expect(error.httpCode).toEqual(400); 18 | expect(error.message).toEqual('Error message'); 19 | expect(error).toBeInstanceOf(BadRequestError); 20 | expect(error).toBeInstanceOf(HttpError); 21 | expect(error).toBeInstanceOf(Error); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/functional/express-custom-error-handling.spec.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { Server as HttpServer } from 'http'; 3 | import HttpStatusCodes from 'http-status-codes'; 4 | import { Get } from '../../src/decorator/Get'; 5 | import { JsonController } from '../../src/decorator/JsonController'; 6 | import { Middleware } from '../../src/decorator/Middleware'; 7 | import { ExpressErrorMiddlewareInterface } from '../../src/driver/express/ExpressErrorMiddlewareInterface'; 8 | import { NotFoundError } from '../../src/http-error/NotFoundError'; 9 | import { createExpressServer, getMetadataArgsStorage, HttpError } from '../../src/index'; 10 | import { axios } from '../utilities/axios'; 11 | import DoneCallback = jest.DoneCallback; 12 | 13 | describe(``, () => { 14 | let expressServer: HttpServer; 15 | 16 | describe('custom express error handling', () => { 17 | let errorHandlerCalled: boolean; 18 | 19 | beforeEach(() => { 20 | errorHandlerCalled = false; 21 | }); 22 | 23 | beforeAll((done: DoneCallback) => { 24 | getMetadataArgsStorage().reset(); 25 | 26 | @Middleware({ type: 'after' }) 27 | class CustomErrorHandler implements ExpressErrorMiddlewareInterface { 28 | error(error: HttpError, request: express.Request, response: express.Response, next: express.NextFunction): any { 29 | errorHandlerCalled = true; 30 | response.status(error.httpCode).send(error.message); 31 | } 32 | } 33 | 34 | @JsonController() 35 | class ExpressErrorHandlerController { 36 | @Get('/blogs') 37 | blogs(): any { 38 | return { 39 | id: 1, 40 | title: 'About me', 41 | }; 42 | } 43 | 44 | @Get('/videos') 45 | videos(): never { 46 | throw new NotFoundError('Videos were not found.'); 47 | } 48 | } 49 | 50 | expressServer = createExpressServer({ 51 | defaultErrorHandler: false, 52 | }).listen(3001, done); 53 | }); 54 | 55 | afterAll((done: DoneCallback) => { 56 | expressServer.close(done); 57 | }); 58 | 59 | it('should not call global error handler middleware if there was no errors', async () => { 60 | expect.assertions(2); 61 | const response = await axios.get('/blogs'); 62 | expect(errorHandlerCalled).toBeFalsy(); 63 | expect(response.status).toEqual(HttpStatusCodes.OK); 64 | }); 65 | 66 | it('should call global error handler middleware', async () => { 67 | expect.assertions(3); 68 | try { 69 | await axios.get('/videos'); 70 | } catch (error) { 71 | expect(errorHandlerCalled).toBeTruthy(); 72 | expect(error.response.status).toEqual(HttpStatusCodes.NOT_FOUND); 73 | expect(error.response.data).toEqual('Videos were not found.'); 74 | } 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/functional/express-global-before-error-handling.spec.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { Server as HttpServer } from 'http'; 3 | import HttpStatusCodes from 'http-status-codes'; 4 | import { Get } from '../../src/decorator/Get'; 5 | import { JsonController } from '../../src/decorator/JsonController'; 6 | import { Middleware } from '../../src/decorator/Middleware'; 7 | import { ExpressErrorMiddlewareInterface } from '../../src/driver/express/ExpressErrorMiddlewareInterface'; 8 | import { ExpressMiddlewareInterface } from '../../src/driver/express/ExpressMiddlewareInterface'; 9 | import { createExpressServer } from '../../src/index'; 10 | import { axios } from '../utilities/axios'; 11 | import DoneCallback = jest.DoneCallback; 12 | 13 | describe(``, () => { 14 | let expressServer: HttpServer; 15 | 16 | describe('custom express global before middleware error handling', () => { 17 | let errorHandlerCalled: boolean; 18 | let errorHandlerName: string; 19 | class CustomError extends Error { 20 | name = 'CustomError'; 21 | message = 'custom error message!'; 22 | } 23 | 24 | beforeEach(() => { 25 | errorHandlerCalled = undefined; 26 | errorHandlerName = undefined; 27 | }); 28 | 29 | beforeAll((done: DoneCallback) => { 30 | @Middleware({ type: 'before' }) 31 | class GlobalBeforeMiddleware implements ExpressMiddlewareInterface { 32 | use(request: express.Request, response: express.Response, next: express.NextFunction): any { 33 | throw new CustomError(); 34 | } 35 | } 36 | 37 | @Middleware({ type: 'after' }) 38 | class CustomErrorHandler implements ExpressErrorMiddlewareInterface { 39 | error(error: any, req: any, res: any, next: any): void { 40 | errorHandlerCalled = true; 41 | errorHandlerName = error.name; 42 | res.status(error.httpCode || 500).send(error.message); 43 | } 44 | } 45 | 46 | @JsonController() 47 | class ExpressErrorHandlerController { 48 | @Get('/answers') 49 | answers(): any { 50 | return { 51 | id: 1, 52 | title: 'My answer', 53 | }; 54 | } 55 | } 56 | 57 | expressServer = createExpressServer().listen(3001, done); 58 | }); 59 | 60 | afterAll((done: DoneCallback) => { 61 | expressServer.close(done); 62 | }); 63 | 64 | it('should call global error handler middleware with CustomError', async () => { 65 | expect.assertions(3); 66 | try { 67 | await axios.get('/answers'); 68 | } catch (error) { 69 | expect(errorHandlerCalled).toBeTruthy(); 70 | expect(errorHandlerName).toEqual('CustomError'); 71 | expect(error.response.status).toEqual(HttpStatusCodes.INTERNAL_SERVER_ERROR); 72 | } 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/functional/express-render-decorator.spec.ts: -------------------------------------------------------------------------------- 1 | import express, { Application as ExpressApplication } from 'express'; 2 | import { Server as HttpServer } from 'http'; 3 | import HttpStatusCodes from 'http-status-codes'; 4 | import mustacheExpress from 'mustache-express'; 5 | import path from 'path'; 6 | import { Controller } from '../../src/decorator/Controller'; 7 | import { Get } from '../../src/decorator/Get'; 8 | import { Render } from '../../src/decorator/Render'; 9 | import { Res } from '../../src/decorator/Res'; 10 | import { createExpressServer, getMetadataArgsStorage } from '../../src/index'; 11 | import { axios } from '../utilities/axios'; 12 | import DoneCallback = jest.DoneCallback; 13 | 14 | describe(``, () => { 15 | let expressServer: HttpServer; 16 | 17 | describe('template rendering', () => { 18 | beforeAll((done: DoneCallback) => { 19 | getMetadataArgsStorage().reset(); 20 | 21 | @Controller() 22 | class RenderController { 23 | @Get('/index') 24 | @Render('render-test-spec.html') 25 | index(): any { 26 | return { 27 | name: 'Routing-controllers', 28 | }; 29 | } 30 | 31 | @Get('/locals') 32 | @Render('render-test-locals-spec.html') 33 | locals(@Res() res: any): any { 34 | res.locals.myVariable = 'my-variable'; 35 | 36 | return { 37 | name: 'Routing-controllers', 38 | }; 39 | } 40 | } 41 | 42 | const resourcePath: string = path.resolve(__dirname, '../resources'); 43 | const expressApplication: ExpressApplication = createExpressServer(); 44 | expressApplication.engine('html', mustacheExpress()); 45 | expressApplication.set('view engine', 'html'); 46 | expressApplication.set('views', resourcePath); 47 | expressApplication.use(express.static(resourcePath)); 48 | expressServer = expressApplication.listen(3001, done); 49 | }); 50 | 51 | afterAll((done: DoneCallback) => { 52 | expressServer.close(done); 53 | }); 54 | 55 | it('should render a template and use given variables', async () => { 56 | expect.assertions(6); 57 | const response = await axios.get('/index'); 58 | expect(response.status).toEqual(HttpStatusCodes.OK); 59 | expect(response.data).toContain(''); 60 | expect(response.data).toContain(''); 61 | expect(response.data).toContain('Routing-controllers'); 62 | expect(response.data).toContain(''); 63 | expect(response.data).toContain(''); 64 | }); 65 | 66 | it('should render a template with given variables and locals variables', async () => { 67 | expect.assertions(7); 68 | const response = await axios.get('/locals'); 69 | expect(response.status).toEqual(HttpStatusCodes.OK); 70 | expect(response.data).toContain(''); 71 | expect(response.data).toContain(''); 72 | expect(response.data).toContain('Routing-controllers'); 73 | expect(response.data).toContain('my-variable'); 74 | expect(response.data).toContain(''); 75 | expect(response.data).toContain(''); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/functional/koa-render-decorator.spec.ts: -------------------------------------------------------------------------------- 1 | import { Render } from '../../src/decorator/Render'; 2 | import { Server as HttpServer } from 'http'; 3 | import HttpStatusCodes from 'http-status-codes'; 4 | import Koa from 'koa'; 5 | import { Controller } from '../../src/decorator/Controller'; 6 | import { Get } from '../../src/decorator/Get'; 7 | import { createKoaServer, getMetadataArgsStorage, Ctx } from '../../src/index'; 8 | import { axios } from '../utilities/axios'; 9 | import koaEjs from '@koa/ejs'; 10 | import path from 'path'; 11 | import DoneCallback = jest.DoneCallback; 12 | 13 | describe(``, () => { 14 | let koaServer: HttpServer; 15 | 16 | describe('koa template rendering', () => { 17 | beforeAll((done: DoneCallback) => { 18 | getMetadataArgsStorage().reset(); 19 | 20 | @Controller() 21 | class RenderController { 22 | @Get('/index') 23 | @Render('ejs-render-test-spec') 24 | index(): any { 25 | return { 26 | name: 'Routing-controllers', 27 | }; 28 | } 29 | 30 | @Get('/locals') 31 | @Render('ejs-render-test-locals-spec') 32 | locals(@Ctx() ctx: any): any { 33 | ctx.locals = { 34 | myVariable: 'my-variable', 35 | }; 36 | 37 | return { 38 | name: 'Routing-controllers', 39 | }; 40 | } 41 | } 42 | 43 | const resourcePath: string = path.resolve(__dirname, '../resources'); 44 | 45 | const koaApp = createKoaServer() as Koa; 46 | koaEjs(koaApp, { 47 | root: resourcePath, 48 | layout: false, 49 | viewExt: 'html', // Auto-appended to template name 50 | cache: false, 51 | debug: true, 52 | }); 53 | 54 | koaServer = koaApp.listen(3001, done); 55 | }); 56 | 57 | afterAll((done: DoneCallback) => { 58 | koaServer.close(done); 59 | }); 60 | 61 | it('should render a template and use given variables', async () => { 62 | expect.assertions(6); 63 | const response = await axios.get('/index'); 64 | expect(response.status).toEqual(HttpStatusCodes.OK); 65 | expect(response.data).toContain(''); 66 | expect(response.data).toContain(''); 67 | expect(response.data).toContain('Routing-controllers'); 68 | expect(response.data).toContain(''); 69 | expect(response.data).toContain(''); 70 | }); 71 | 72 | it('should render a template with given variables and locals variables', async () => { 73 | expect.assertions(7); 74 | const response = await axios.get('/locals'); 75 | expect(response.status).toEqual(HttpStatusCodes.OK); 76 | expect(response.data).toContain(''); 77 | expect(response.data).toContain(''); 78 | expect(response.data).toContain('Routing-controllers'); 79 | expect(response.data).toContain('my-variable'); 80 | expect(response.data).toContain(''); 81 | expect(response.data).toContain(''); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/functional/koa-trailing-slash.spec.ts: -------------------------------------------------------------------------------- 1 | import { Server as HttpServer } from 'http'; 2 | import HttpStatusCodes from 'http-status-codes'; 3 | import { Controller } from '../../src/decorator/Controller'; 4 | import { Get } from '../../src/decorator/Get'; 5 | import { createKoaServer, getMetadataArgsStorage } from '../../src/index'; 6 | import { axios } from '../utilities/axios'; 7 | import DoneCallback = jest.DoneCallback; 8 | 9 | describe(``, () => { 10 | let koaServer: HttpServer; 11 | 12 | describe('koa trailing slashes', () => { 13 | beforeEach((done: DoneCallback) => { 14 | getMetadataArgsStorage().reset(); 15 | 16 | @Controller('/posts') 17 | class PostController { 18 | @Get('/') 19 | getAll(): string { 20 | return 'All posts'; 21 | } 22 | } 23 | 24 | koaServer = createKoaServer().listen(3001, done); 25 | }); 26 | 27 | afterEach((done: DoneCallback) => { 28 | koaServer.close(done); 29 | }); 30 | 31 | it('get should respond to request without a traling slash', async () => { 32 | expect.assertions(3); 33 | const response = await axios.get('/posts'); 34 | expect(response.status).toEqual(HttpStatusCodes.OK); 35 | expect(response.headers['content-type']).toEqual('text/html; charset=utf-8'); 36 | expect(response.data).toEqual('All posts'); 37 | }); 38 | 39 | it('get should respond to request with a traling slash', async () => { 40 | expect.assertions(3); 41 | const response = await axios.get('/posts/'); 42 | expect(response.status).toEqual(HttpStatusCodes.OK); 43 | expect(response.headers['content-type']).toEqual('text/html; charset=utf-8'); 44 | expect(response.data).toEqual('All posts'); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/functional/load-from-directory.spec.ts: -------------------------------------------------------------------------------- 1 | import { Server as HttpServer } from 'http'; 2 | import HttpStatusCodes from 'http-status-codes'; 3 | import { Controller } from '../../src/decorator/Controller'; 4 | import { Get } from '../../src/decorator/Get'; 5 | import { createExpressServer, getMetadataArgsStorage } from '../../src/index'; 6 | import { defaultFakeService } from '../fakes/global-options/FakeService'; 7 | import { axios } from '../utilities/axios'; 8 | import DoneCallback = jest.DoneCallback; 9 | 10 | describe(``, () => { 11 | let expressServer: HttpServer; 12 | 13 | describe('loading all controllers from the given directories', () => { 14 | beforeAll((done: DoneCallback) => { 15 | getMetadataArgsStorage().reset(); 16 | expressServer = createExpressServer({ 17 | controllers: [ 18 | __dirname + '/../fakes/global-options/first-controllers/**/*{.js,.ts}', 19 | __dirname + '/../fakes/global-options/second-controllers/*{.js,.ts}', 20 | ], 21 | }).listen(3001, done); 22 | }); 23 | 24 | afterAll((done: DoneCallback) => { 25 | expressServer.close(done); 26 | }); 27 | 28 | it('should load all controllers', async () => { 29 | expect.assertions(10); 30 | let response = await axios.get('/posts'); 31 | expect(response.status).toEqual(HttpStatusCodes.OK); 32 | expect(response.data).toEqual([ 33 | { id: 1, title: '#1' }, 34 | { id: 2, title: '#2' }, 35 | ]); 36 | 37 | response = await axios.get('/questions'); 38 | expect(response.status).toEqual(HttpStatusCodes.OK); 39 | expect(response.data).toEqual([ 40 | { id: 1, title: '#1' }, 41 | { id: 2, title: '#2' }, 42 | ]); 43 | 44 | response = await axios.get('/answers'); 45 | expect(response.status).toEqual(HttpStatusCodes.OK); 46 | expect(response.data).toEqual([ 47 | { id: 1, title: '#1' }, 48 | { id: 2, title: '#2' }, 49 | ]); 50 | 51 | response = await axios.get('/photos'); 52 | expect(response.status).toEqual(HttpStatusCodes.OK); 53 | expect(response.data).toEqual('Hello photos'); 54 | 55 | response = await axios.get('/videos'); 56 | expect(response.status).toEqual(HttpStatusCodes.OK); 57 | expect(response.data).toEqual('Hello videos'); 58 | }); 59 | }); 60 | 61 | describe('loading all express middlewares and error handlers from the given directories', () => { 62 | beforeAll((done: DoneCallback) => { 63 | getMetadataArgsStorage().reset(); 64 | 65 | @Controller() 66 | class ExpressMiddlewareDirectoriesController { 67 | @Get('/publications') 68 | publications(): any[] { 69 | return []; 70 | } 71 | 72 | @Get('/articles') 73 | articles(): any[] { 74 | throw new Error('Cannot load articles'); 75 | } 76 | } 77 | 78 | expressServer = createExpressServer({ 79 | middlewares: [__dirname + '/../fakes/global-options/express-middlewares/**/*{.js,.ts}'], 80 | }).listen(3001, done); 81 | }); 82 | 83 | afterAll((done: DoneCallback) => { 84 | expressServer.close(done); 85 | }); 86 | 87 | beforeEach(() => defaultFakeService.reset()); 88 | 89 | it('should succeed', async () => { 90 | expect.assertions(6); 91 | const response = await axios.get('/publications'); 92 | expect(response.status).toEqual(HttpStatusCodes.OK); 93 | expect(defaultFakeService.postMiddlewareCalled).toBeTruthy(); 94 | expect(defaultFakeService.questionMiddlewareCalled).toBeTruthy(); 95 | expect(defaultFakeService.questionErrorMiddlewareCalled).toBeFalsy(); 96 | expect(defaultFakeService.fileMiddlewareCalled).toBeFalsy(); 97 | expect(defaultFakeService.videoMiddlewareCalled).toBeFalsy(); 98 | }); 99 | 100 | it('should fail', async () => { 101 | expect.assertions(6); 102 | try { 103 | await axios.get('/articles'); 104 | } catch (error) { 105 | expect(error.response.status).toEqual(HttpStatusCodes.INTERNAL_SERVER_ERROR); 106 | expect(defaultFakeService.postMiddlewareCalled).toBeTruthy(); 107 | expect(defaultFakeService.questionMiddlewareCalled).toBeTruthy(); 108 | expect(defaultFakeService.questionErrorMiddlewareCalled).toBeTruthy(); 109 | expect(defaultFakeService.fileMiddlewareCalled).toBeFalsy(); 110 | expect(defaultFakeService.videoMiddlewareCalled).toBeFalsy(); 111 | } 112 | }); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/functional/middlewares-order.spec.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { Server as HttpServer } from 'http'; 3 | import HttpStatusCodes from 'http-status-codes'; 4 | import { Controller } from '../../src/decorator/Controller'; 5 | import { Get } from '../../src/decorator/Get'; 6 | import { Middleware } from '../../src/decorator/Middleware'; 7 | import { ExpressMiddlewareInterface } from '../../src/driver/express/ExpressMiddlewareInterface'; 8 | import { createExpressServer, getMetadataArgsStorage } from '../../src/index'; 9 | import { axios } from '../utilities/axios'; 10 | import DoneCallback = jest.DoneCallback; 11 | 12 | describe(``, () => { 13 | let expressServer: HttpServer; 14 | 15 | describe('loaded direct from array', () => { 16 | let middlewaresOrder: number[]; 17 | 18 | beforeEach(() => { 19 | middlewaresOrder = []; 20 | }); 21 | 22 | beforeAll((done: DoneCallback) => { 23 | getMetadataArgsStorage().reset(); 24 | 25 | @Middleware({ type: 'after' }) 26 | class ThirdAfterMiddleware implements ExpressMiddlewareInterface { 27 | use(request: express.Request, response: express.Response, next: express.NextFunction): any { 28 | middlewaresOrder.push(3); 29 | next(); 30 | } 31 | } 32 | 33 | @Middleware({ type: 'after' }) 34 | class FirstAfterMiddleware implements ExpressMiddlewareInterface { 35 | use(request: express.Request, response: express.Response, next: express.NextFunction): any { 36 | middlewaresOrder.push(1); 37 | next(); 38 | } 39 | } 40 | 41 | @Middleware({ type: 'after' }) 42 | class SecondAfterMiddleware implements ExpressMiddlewareInterface { 43 | use(request: express.Request, response: express.Response, next: express.NextFunction): any { 44 | middlewaresOrder.push(2); 45 | next(); 46 | } 47 | } 48 | 49 | @Controller() 50 | class ExpressMiddlewareController { 51 | @Get('/test') 52 | test(): string { 53 | return 'OK'; 54 | } 55 | } 56 | 57 | expressServer = createExpressServer({ 58 | middlewares: [FirstAfterMiddleware, SecondAfterMiddleware, ThirdAfterMiddleware], 59 | }).listen(3001, done); 60 | }); 61 | 62 | afterAll((done: DoneCallback) => { 63 | expressServer.close(done); 64 | }); 65 | 66 | it('should call middlewares in order defined by items order', async () => { 67 | expect.assertions(4); 68 | const response = await axios.get('/test'); 69 | expect(response.status).toEqual(HttpStatusCodes.OK); 70 | expect(middlewaresOrder[0]).toEqual(1); 71 | expect(middlewaresOrder[1]).toEqual(2); 72 | expect(middlewaresOrder[2]).toEqual(3); 73 | }); 74 | }); 75 | 76 | describe('specified by priority option', () => { 77 | let middlewaresOrder: number[]; 78 | 79 | beforeEach(() => { 80 | middlewaresOrder = []; 81 | }); 82 | 83 | beforeAll((done: DoneCallback) => { 84 | getMetadataArgsStorage().reset(); 85 | 86 | @Middleware({ type: 'after', priority: 0 }) 87 | class ThirdAfterMiddleware implements ExpressMiddlewareInterface { 88 | use(request: express.Request, response: express.Response, next: express.NextFunction): any { 89 | middlewaresOrder.push(3); 90 | next(); 91 | } 92 | } 93 | 94 | @Middleware({ type: 'after', priority: 8 }) 95 | class FirstAfterMiddleware implements ExpressMiddlewareInterface { 96 | use(request: express.Request, response: express.Response, next: express.NextFunction): any { 97 | middlewaresOrder.push(1); 98 | next(); 99 | } 100 | } 101 | 102 | @Middleware({ type: 'after', priority: 4 }) 103 | class SecondAfterMiddleware implements ExpressMiddlewareInterface { 104 | use(request: express.Request, response: express.Response, next: express.NextFunction): any { 105 | middlewaresOrder.push(2); 106 | next(); 107 | } 108 | } 109 | 110 | @Controller() 111 | class ExpressMiddlewareController { 112 | @Get('/test') 113 | test(): string { 114 | return 'OK'; 115 | } 116 | } 117 | 118 | expressServer = createExpressServer({ 119 | middlewares: [SecondAfterMiddleware, ThirdAfterMiddleware, FirstAfterMiddleware], 120 | }).listen(3001, done); 121 | }); 122 | 123 | afterAll((done: DoneCallback) => { 124 | expressServer.close(done); 125 | }); 126 | 127 | it('should call middlewares in order defined by priority parameter of decorator', async () => { 128 | expect.assertions(4); 129 | const response = await axios.get('/test'); 130 | expect(response.status).toEqual(HttpStatusCodes.OK); 131 | expect(middlewaresOrder[0]).toEqual(1); 132 | expect(middlewaresOrder[1]).toEqual(2); 133 | expect(middlewaresOrder[2]).toEqual(3); 134 | }); 135 | }); 136 | }); 137 | -------------------------------------------------------------------------------- /test/functional/redirect-decorator.spec.ts: -------------------------------------------------------------------------------- 1 | import { Server as HttpServer } from 'http'; 2 | import HttpStatusCodes from 'http-status-codes'; 3 | import { Get } from '../../src/decorator/Get'; 4 | import { JsonController } from '../../src/decorator/JsonController'; 5 | import { Param } from '../../src/decorator/Param'; 6 | import { Redirect } from '../../src/decorator/Redirect'; 7 | import { createExpressServer, getMetadataArgsStorage } from '../../src/index'; 8 | import { axios } from '../utilities/axios'; 9 | import DoneCallback = jest.DoneCallback; 10 | 11 | describe(``, () => { 12 | let expressServer: HttpServer; 13 | 14 | describe('dynamic redirect', function () { 15 | beforeAll((done: DoneCallback) => { 16 | getMetadataArgsStorage().reset(); 17 | 18 | @JsonController('/users') 19 | class TestController { 20 | @Get('/:id') 21 | getOne(@Param('id') id: string): any { 22 | return { 23 | login: id, 24 | }; 25 | } 26 | } 27 | 28 | @JsonController() 29 | class RedirectController { 30 | @Get('/template') 31 | @Redirect('/users/:owner') 32 | template(): any { 33 | return { owner: 'pleerock', repo: 'routing-controllers' }; 34 | } 35 | 36 | @Get('/original') 37 | @Redirect('/users/pleerock') 38 | original(): void { 39 | // Empty 40 | } 41 | 42 | @Get('/override') 43 | @Redirect('https://api.github.com') 44 | override(): string { 45 | return '/users/pleerock'; 46 | } 47 | } 48 | 49 | expressServer = createExpressServer().listen(3001, done); 50 | }); 51 | 52 | afterAll((done: DoneCallback) => { 53 | expressServer.close(done); 54 | }); 55 | 56 | it('using template', async () => { 57 | expect.assertions(2); 58 | const response = await axios.get('/template'); 59 | expect(response.status).toEqual(HttpStatusCodes.OK); 60 | expect(response.data.login).toEqual('pleerock'); 61 | }); 62 | 63 | it('using override', async () => { 64 | expect.assertions(2); 65 | const response = await axios.get('/override'); 66 | expect(response.status).toEqual(HttpStatusCodes.OK); 67 | expect(response.data.login).toEqual('pleerock'); 68 | }); 69 | 70 | it('using original', async () => { 71 | expect.assertions(2); 72 | const response = await axios.get('/original'); 73 | expect(response.status).toEqual(HttpStatusCodes.OK); 74 | expect(response.data.login).toEqual('pleerock'); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/functional/special-result-send.spec.ts: -------------------------------------------------------------------------------- 1 | import { createReadStream } from 'fs'; 2 | import { Server as HttpServer } from 'http'; 3 | import HttpStatusCodes from 'http-status-codes'; 4 | import * as path from 'path'; 5 | import { ContentType } from '../../src/decorator/ContentType'; 6 | import { Get } from '../../src/decorator/Get'; 7 | import { JsonController } from '../../src/decorator/JsonController'; 8 | import { createExpressServer, getMetadataArgsStorage } from '../../src/index'; 9 | import { axios } from '../utilities/axios'; 10 | import DoneCallback = jest.DoneCallback; 11 | import ReadableStream = NodeJS.ReadableStream; 12 | 13 | describe(``, () => { 14 | let expressServer: HttpServer; 15 | 16 | describe('special result value treatment', () => { 17 | const rawData = [0xff, 0x66, 0xaa, 0xcc]; 18 | 19 | beforeAll((done: DoneCallback) => { 20 | getMetadataArgsStorage().reset(); 21 | 22 | @JsonController() 23 | class HandledController { 24 | @Get('/stream') 25 | @ContentType('text/plain') 26 | getStream(): ReadableStream { 27 | return createReadStream(path.resolve(__dirname, '../resources/sample-text-file.txt')); 28 | } 29 | 30 | @Get('/buffer') 31 | @ContentType('application/octet-stream') 32 | getBuffer(): Buffer { 33 | return Buffer.from(rawData); 34 | } 35 | 36 | @Get('/array') 37 | @ContentType('application/octet-stream') 38 | getUIntArray(): Uint8Array { 39 | return new Uint8Array(rawData); 40 | } 41 | } 42 | 43 | expressServer = createExpressServer().listen(3001, done); 44 | }); 45 | 46 | afterAll((done: DoneCallback) => { 47 | expressServer.close(done); 48 | }); 49 | 50 | it('should pipe stream to response', async () => { 51 | // expect.assertions(3); 52 | expect.assertions(2); 53 | const response = await axios.get('/stream', { responseType: 'stream' }); 54 | // TODO: Fix me, I believe RC is working ok, I don't know how to get the buffer 55 | // of the response 56 | // expect(response.data).toBe('Hello World!'); 57 | expect(response.status).toEqual(HttpStatusCodes.OK); 58 | expect(response.headers['content-type']).toEqual('text/plain; charset=utf-8'); 59 | }); 60 | 61 | it('should send raw binary data from Buffer', async () => { 62 | expect.assertions(3); 63 | const response = await axios.get('/buffer'); 64 | expect(response.status).toEqual(HttpStatusCodes.OK); 65 | expect(response.headers['content-type']).toEqual('application/octet-stream'); 66 | expect(response.data).toEqual(Buffer.from(rawData).toString()); 67 | }); 68 | 69 | it('should send raw binary data from UIntArray', async () => { 70 | expect.assertions(3); 71 | const response = await axios.get('/array'); 72 | expect(response.status).toEqual(HttpStatusCodes.OK); 73 | expect(response.headers['content-type']).toEqual('application/octet-stream'); 74 | expect(response.data).toEqual(Buffer.from(rawData).toString()); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/resources/ejs-render-test-locals-spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= name %> 5 | <%= myVariable %> 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/resources/ejs-render-test-spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= name %> 4 | 5 | -------------------------------------------------------------------------------- /test/resources/render-test-locals-spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ name }} 5 | {{ myVariable }} 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/resources/render-test-spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ name }} 4 | 5 | -------------------------------------------------------------------------------- /test/resources/sample-text-file.txt: -------------------------------------------------------------------------------- 1 | Hello World! -------------------------------------------------------------------------------- /test/utilities/axios.ts: -------------------------------------------------------------------------------- 1 | import Axios, { AxiosInstance } from 'axios'; 2 | import { Agent } from 'http'; 3 | 4 | export const axios: AxiosInstance = Axios.create({ 5 | baseURL: 'http://localhost:3001/', 6 | httpAgent: new Agent({ keepAlive: false }), // disable keepAlive until axios is fixed 7 | }); 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "target": "es2018", 6 | "lib": ["es2018"], 7 | "outDir": "build/node", 8 | "rootDir": "./src", 9 | "strict": true, 10 | "sourceMap": true, 11 | "inlineSources": true, 12 | "removeComments": false, 13 | "esModuleInterop": true, 14 | "experimentalDecorators": true, 15 | "emitDecoratorMetadata": true, 16 | "forceConsistentCasingInFileNames": true 17 | }, 18 | "exclude": ["build", "node_modules", "sample", "**/*.spec.ts", "test/**"] 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.prod.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.prod.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "outDir": "build/cjs" 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.prod.esm2015.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.prod.json", 4 | "compilerOptions": { 5 | "module": "ES2015", 6 | "outDir": "build/esm2015", 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "strict": false, 5 | "declaration": false, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.prod.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.prod.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "outDir": "build/types", 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "strict": false, 6 | "strictPropertyInitialization": false, 7 | "sourceMap": true, 8 | "removeComments": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | }, 12 | "exclude": ["node_modules"] 13 | } 14 | --------------------------------------------------------------------------------