{"id":17230056,"url":"https://github.com/robinbuschmann/express-controllers","last_synced_at":"2025-04-14T01:42:26.848Z","repository":{"id":123808898,"uuid":"86728728","full_name":"RobinBuschmann/express-controllers","owner":"RobinBuschmann","description":"Express middleware for resolving controllers with api versioning support","archived":false,"fork":false,"pushed_at":"2017-09-18T13:03:47.000Z","size":94,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-11T02:44:14.810Z","etag":null,"topics":["controller","expressjs","middleware"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RobinBuschmann.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-30T17:13:32.000Z","updated_at":"2023-01-18T00:34:27.000Z","dependencies_parsed_at":null,"dependency_job_id":"8b9e9138-594e-45a6-8aaa-eec3e744c6d1","html_url":"https://github.com/RobinBuschmann/express-controllers","commit_stats":{"total_commits":29,"total_committers":3,"mean_commits":9.666666666666666,"dds":"0.27586206896551724","last_synced_commit":"2f3bf1f4690d580b18635621fc831c33c15826e4"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobinBuschmann%2Fexpress-controllers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobinBuschmann%2Fexpress-controllers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobinBuschmann%2Fexpress-controllers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobinBuschmann%2Fexpress-controllers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RobinBuschmann","download_url":"https://codeload.github.com/RobinBuschmann/express-controllers/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248809044,"owners_count":21164895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["controller","expressjs","middleware"],"created_at":"2024-10-15T04:52:19.384Z","updated_at":"2025-04-14T01:42:26.827Z","avatar_url":"https://github.com/RobinBuschmann.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/RobinBuschmann/express-controllers.png?branch=master)](https://travis-ci.org/RobinBuschmann/express-controllers)\n[![codecov](https://codecov.io/gh/RobinBuschmann/express-controllers/branch/master/graph/badge.svg)](https://codecov.io/gh/RobinBuschmann/express-controllers)\n[![NPM](https://img.shields.io/npm/v/express-versioning.svg)](https://www.npmjs.com/package/express-versioning)\n[![Dependency Status](https://img.shields.io/david/RobinBuschmann/express-controllers.svg)](https://www.npmjs.com/package/express-versioning)\n\n# express-versioning\nExpress middleware for resolving controllers with api versioning support.\n\n - [Getting started](#getting-started)\n - [Abstract controllers](#abstract-controllers)\n - [Complex file structures](#complex-file-structures)\n - [Defining routes directly in controllers with TypeScript](#defining-routes-directly-in-controllers-with-typescript)\n - [API](#api)\n - [Why?](#why)\n\n### Installation\n```\nnpm install express-versioning --save\n```\n\n## Getting Started\n1. **Create file structure for controllers**\n```\n - controllers/\n    - v1/\n        - users.js\n        - posts.js\n        - comments.js\n    - v2/\n        - users.js\n        ...\n```\nThe name of files should have the same name as the resource, which will appear in the path of the route. So that\n`users.js` =\u003e `/users`. If you want to use any prefix or suffix for your file names see [controllerPattern](#api)\n\n2. **Setup controllers**\n    1. **Object literal approach**\n    ```typescript\n    // v1/users.js\n     \n    const users = {\n        \n       getUser(req, res, next) { /* ... */ },\n    \n       getUsers(req, res, next) { /* ... */ }\n    };\n     \n    export default users;\n    \n    // v2/users.js\n    import v1User from '../v1/users.js'\n    \n    const users = Object.create(v1User); // link usersV2 to usersV1 via prototype chain\n    // override\n    users.getUser = function (req, res, next) { /* ... */ };\n    \n    export default users;\n    \n    ```\n    To make functionality of previous versions available in a newly created version, the newly created version has to\n    be linked to the previous version. This is achieved with `Object.create(v1User)`. \n    If you don't want to make previous functionality available, don't link the controllers\n    \n    2. **Class approach**\n     ```typescript\n     // v1/users.js\n      \n     export default class UserController {\n  \n       getUser(req, res, next) { /* ... */ }\n      \n       getUsers(req, res, next) { /* ... */ }\n     }\n     \n     // v2/users.js\n     import V1UserController from '../v1/users.js'\n     \n     export class UserController extends V1UserController {\n   \n       // override\n       getUser(req, res, next) { /* ... */ }\n    \n     }\n     \n     ```\n    To make functionality of previous versions available in a newly created version, the newly created version has to\n    extend the controller of the previous version.\n    If you don't want to make previous functionality available, don't extend the controllers of the previous version\n    \n    If you're using **dependency injection**, you can set a getter function or an injector (see [here](#api))\n    \n**If you prefer using named exports, make sure, that the filename and the name of the exported controller are the same**\n\n3. **Setup middleware and routes**\n```typescript\nimport {controllers} from 'express-versioning';\n\napp.use(controllers({\n  path: __dirname + '/controllers'\n}));\n\napp.get('/:version/users', (req, res, next) =\u003e req.controller.getUsers(req, res, next));\napp.get('/:version/users/:id', (req, res, next) =\u003e req.controller.getUser(req, res, next));\n```\n\n## Abstract controllers\nIf you want to define abstract controllers for you routes, you can do so by creating an abstract folder on the\nsame level as the version folders:\n```\n - controllers/\n    - abstract/\n        - users.js\n        - posts.js\n        - comments.js\n    - v1/\n        - users.js\n        - posts.js\n        - comments.js\n    - v2/\n        - users.js\n        ...\n```\nThe name of the abstract folder can be changed (see [here](#api)). \n\n## Complex file structures\nIf your controllers are structured much more complex like:\n```\n - controllers/\n    - organization-a/\n        - v1/\n          - users.js\n        - v2/\n          - users.js\n    - organization-b/\n        - v1/\n          - users.js\n          - sub-organization/\n            - documents.js\n        ...\n```\n`express-versioning` can also handle this for you and will resolve the controllers of the example to the following routes:\n```\norganization-a/:version/users\norganization-b/:version/users\norganization-b/:version/sub-organiuation/documents\n```\n\n## Defining routes directly in controllers with TypeScript\nWith TypeScript you're able to define the routes of its corresponding route handlers directly in the controller class\nof these handlers. Therefore annotations come into play.\n\n### Configuration\nTo use this feature, you need to install `reflect-metadata` and need to set some flags in your `tsconfig.json`:\n\n#### `reflect-metadata`\n```\nnpm install reflect-metadata --save\n```\n\n#### `tsconfig.json`\n```json\n\"experimentalDecorators\": true,\n\"emitDecoratorMetadata\": true\n```\n\n### Usage\nTo define get routes, annotate the appropriate route handlers with a `@Get` annotation. The same works for all http\nmethods, that are supported by express. Please notice, that you should not use the resource name in the path, since\nit is already set due to the filename.\n```typescript\n// /v1/UserController.js\nimport {Request, Response, NextFunction} from 'express';\nimport {Get, Post, Put} from 'express-versioning';\n\nexport class UserController {\n\n  @Get('/:id')\n  getUser(req: Request, res: Response, next: NextFunction) { /* ... */ }\n\n  @Get\n  getUsers(req: Request, res: Response, next: NextFunction) { /* ... */ }\n\n  @Get('/:id/posts')\n  getUserPosts(req: Request, res: Response, next: NextFunction) { /* ... */ }\n\n  @Post\n  postUser(req: Request, res: Response, next: NextFunction) { /* ... */ }\n\n  @Put('/:id')\n  putUser(req: Request, res: Response, next: NextFunction) { /* ... */ }\n}\n```\n#### Configure middleware\n`resolveRouteHandler` need to be set to `true`.\n```typescript\nimport {controllers} from 'express-versioning';\n\napp.use(controllers({\n  path: __dirname + '/controllers',\n  resolveRouteHandler: true,\n  controllerPattern: /(.*?)Controller/\n}));\n```\n\n#### Overriding route handlers\nWhen overriding route handlers of previous versions, you must not define the route for its handler again. But must\ninstead use the `@OverrideRouteHandler` annotation. Otherwise `express-versioning` throws an error.\nThis will ensures, that route handlers will not be overridden by accident. Furthermore, it makes clear, that the\n`@OverrideRouteHandler` annotated function *is* a route handler.\n\n```typescript\n// /v2/UserController.js\nimport {OverrideRouteHandler} from 'express-versioning';\nimport {UserController as V1UserController} from '../v1/UserController';\n\nexport class UserController extends V1UserController {\n\n  @OverrideRouteHandler\n  getUser(req: Request, res: Response, next: NextFunction) { /* ... */ }\n}\n```\n\n#### Set resource name explicitly\nIf you don't want the resource name to be inferred by file name automatically, you can do so, by setting the resource\nname explicitly with `@Resource`:\n```typescript\nimport {Resource} from 'express-versioning';\n\n@Resource('users')\nexport class UserController {\n\n}\n```\nor with starting \"/\"\n```typescript\nimport {Resource} from 'express-versioning';\n\n@Resource('/users')\nexport class UserController {\n\n}\n```\n\n## API\n\n### `controllers` options\n \n```typescript\n/**\n * Path to controllers\n */\npath: string;\n\n/**\n * Regex pattern to recognize a version folder\n * @default /^(v\\d.*)$/\n */\nversionPattern?: RegExp;\n\n/**\n * Regex pattern to recognize controller files\n * @default /^(.*?)$/\n */\ncontrollerPattern?: RegExp;\n\n/**\n * Name of directory in which abstract controllers can be\n * found\n * @default abstract\n */\nabstractDir?: string;\n\n/**\n * Prints some info to console if true. Default is false\n * @default false\n */\ndebug?: boolean;\n\n/**\n * Indicates if routes handlers should be resolved from\n * controllers automatically or not\n * @default false\n */\nresolveRouteHandler?: boolean;\n\n/**\n * Injector to inject controller class instance\n */\ninjector?: {get\u003cT\u003e(model: any): T};\n\n/**\n * Inject function to inject a controller class instance\n */\ninject?\u003cT\u003e(model: any): T;\n```\n\n### Annotations\n\n```typescript\n\n/**\n * Stores http method and path as metadata for target prototype\n */\n\n@Get\n@Get(path: PathParams)\n\n@Put\n@Put(path: PathParams)\n\n@Post\n@Post(path: PathParams)\n\n// ... works for all http methods, that are supported by express\n\n```\n\n## Why\nWhen creating a REST api with version support, there will be some problems you will face:\n\nThe definition of the same routes for all versions again and again and again ... and the need to care about which \ncontroller should be used for which version:\n```typescript\napp.get('/v1/users', (req, res, next) =\u003e usersController.getUsers(req, res, next));\napp.get('/v2/users', (req, res, next) =\u003e usersController1.getUsers(req, res, next));\napp.get('/v3/users', (req, res, next) =\u003e usersController2.getUsers(req, res, next));\n/* ... */\n```\n\nThe definition of routes of previous versions for next versions.\nWhen creating a next version, not all routes should be created again for a new version. Especially when not all \nroutes have changed compared to the previous version:\n```typescript\n// version 1\napp.get('/v1/users', (req, res, next) =\u003e usersController.getUsers(req, res, next));\napp.get('/v1/users/:id', (req, res, next) =\u003e usersController.getUser(req, res, next));\napp.post('/v1/users', (req, res, next) =\u003e usersController.postUser(req, res, next));\n\n// version 2\napp.get('/v2/users', (req, res, next) =\u003e usersController2.getUsers(req, res, next));\n\n// no changes here (see \"userController\" instead of \"userController2\")\napp.get('/v2/users/:id', (req, res, next) =\u003e usersController.getUser(req, res, next));\n\napp.post('/v2/users', (req, res, next) =\u003e usersController2.postUser(req, res, next));\n```\nRoute `/v2/users/:id` has to be defined again, despite of nothing has changed in version 2 in `getUser` implementation. \nBut to make this endpoint also available in version 2, we had to do so.\n\n**Since DRY is not satisfied, all these issues will probably result in bugs**\n\n`express-versioning` solves these problems for you. So that you don't need to repeat yourself.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobinbuschmann%2Fexpress-controllers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobinbuschmann%2Fexpress-controllers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobinbuschmann%2Fexpress-controllers/lists"}