{"id":13716101,"url":"https://github.com/Bessonov/node-http-router","last_synced_at":"2025-05-07T05:32:07.895Z","repository":{"id":38984185,"uuid":"216270504","full_name":"Bessonov/node-http-router","owner":"Bessonov","description":"Fantastic type-safe router for Node.js, micro and other servers","archived":false,"fork":false,"pushed_at":"2024-05-14T23:28:43.000Z","size":285,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-13T09:01:55.579Z","etag":null,"topics":["api","http","match","micro","microservices","nanoservices","nodejs","rest","route","router","routing","server","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Bessonov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2019-10-19T21:06:09.000Z","updated_at":"2023-04-11T23:53:30.000Z","dependencies_parsed_at":"2024-05-15T15:46:07.420Z","dependency_job_id":"afdde782-0dac-475e-a7c7-9dd811b53b78","html_url":"https://github.com/Bessonov/node-http-router","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bessonov%2Fnode-http-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bessonov%2Fnode-http-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bessonov%2Fnode-http-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bessonov%2Fnode-http-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Bessonov","download_url":"https://codeload.github.com/Bessonov/node-http-router/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252823130,"owners_count":21809700,"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":["api","http","match","micro","microservices","nanoservices","nodejs","rest","route","router","routing","server","typescript"],"created_at":"2024-08-03T00:01:07.066Z","updated_at":"2025-05-07T05:32:04.872Z","avatar_url":"https://github.com/Bessonov.png","language":"TypeScript","funding_links":[],"categories":["Modules"],"sub_categories":["Routing"],"readme":"Router for Node.js, micro and other use cases\n=============================================\n\n[![Project is](https://img.shields.io/badge/Project%20is-fantastic-ff69b4.svg)](https://github.com/Bessonov/node-http-router)\n[![Build Status](https://api.travis-ci.org/Bessonov/node-http-router.svg?branch=master)](https://travis-ci.org/Bessonov/node-http-router)\n[![License](http://img.shields.io/:license-MIT-blue.svg)](https://raw.githubusercontent.com/Bessonov/node-http-router/master/LICENSE)\n\nThis router is intended to be used with native node http interface. Features:\n- Written in TypeScript with focus on type safety.\n- Extensible via [`Matcher`](src/matchers/Matcher.ts) and [`MatchResult`](src/matchers/MatchResult.ts) interfaces.\n- Works with [native node http server](#usage-with-native-node-http-server).\n- Works with [micro](#usage-with-micro).\n- Offers a set of matchers:\n  - [`MethodMatcher`](#methodmatcher)\n  - [`ExactUrlPathnameMatcher`](#exacturlpathnamematcher)\n  - [`ExactQueryMatcher`](#exactquerymatcher)\n  - Powerful [`RegExpUrlMatcher`](#regexpurlmatcher)\n  - Convenient [`EndpointMatcher`](#endpointmatcher)\n  - `AndMatcher` and `OrMatcher`\n- Can be used with [path-to-regexp](https://github.com/pillarjs/path-to-regexp).\n- Work with other servers? Tell it me!\n\nFrom 2.0.0 the router isn't tied to node or even http anymore! Although the primary use case is still node's request routing, you can use it for use cases like event processing.\n\n## Sponsoring\n\nContact me if you want to become a sponsor or need paid support.\n\nSponsored by [Superlative GmbH](https://superlative.gmbh)\n\n![Superlative GmbH](./sponsors/superlative.gmbh.png)\n\n## Installation\n\nChoose for one of your favourite package manager:\n\n```bash\nnpm install @bessonovs/node-http-router\nyarn add @bessonovs/node-http-router\npnpm add @bessonovs/node-http-router\n```\n\n## Changelog\n\nSee [releases](https://github.com/Bessonov/node-http-router/releases).\n\n## Documentation and examples\n\n### Binding\n\nThe router doesn't depends on the native http interfaces like `IncomingMessage` and `ServerResponse`. Therefore, you can use it for everything. Below are some use cases.\n\n#### Usage with native node http server\n\n```typescript\nconst router = new NodeHttpRouter()\n\nconst server = http.createServer(router.serve).listen(8080, 'localhost')\n\nrouter.addRoute({\n\tmatcher: new ExactUrlPathnameMatcher(['/hello']),\n\thandler: () =\u003e 'Hello kitty!',\n})\n\n// 404 handler\nrouter.addRoute({\n\tmatcher: new BooleanMatcher(true),\n\thandler: ({ data: { res } }) =\u003e send(res, 404)\n})\n```\n\nSee [full example](src/examples/node.ts) and [native node http server](https://nodejs.org/api/http.html#http_class_http_server) documentation.\n\n#### Usage with micro server\n\n[micro](https://github.com/vercel/micro) is a very lightweight layer around the native node http server with some convenience methods.\n\n```typescript\nimport {\n\tsend,\n\tserve,\n} from 'micro'\n\nconst router = new NodeHttpRouter()\n\nhttp.createServer(serve(router.serve)).listen(8080, 'localhost')\n\nrouter.addRoute({\n\tmatcher: new ExactUrlPathnameMatcher(['/hello']),\n\thandler: () =\u003e 'Hello kitty!',\n})\n\n// 404 handler\nrouter.addRoute({\n\tmatcher: new BooleanMatcher(true),\n\thandler: ({ data: { res } }) =\u003e send(res, 404)\n})\n```\n\nSee [full example](src/examples/micro.ts).\n\n#### Usage for event processing or generic use case\n\n```typescript\n// Custom type\ntype MyEvent = {\n\tname: 'test1',\n} | {\n\tname: 'test2',\n} | {\n\tname: 'invalid',\n}\n\nconst eventRouter = new Router\u003cMyEvent\u003e()\n\neventRouter.addRoute({\n\t// define matchers for event processing\n\tmatcher: ({\n\t\tmatch(params: MyEvent): MatchResult\u003cnumber\u003e {\n\t\t\tconst result = /^test(?\u003cnum\u003e\\d+)$/.exec(params.name)\n\t\t\tif (result?.groups?.num) {\n\t\t\t\treturn {\n\t\t\t\t\tmatched: true,\n\t\t\t\t\tresult: parseInt(result.groups.num)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tmatched: false,\n\t\t\t}\n\t\t},\n\t}),\n\t// define event handler for matched events\n\thandler({ data, match: { result } }) {\n\t\treturn `the event ${data.name} has number ${result}`\n\t}\n})\n\n// add default handler\neventRouter.addRoute({\n\tmatcher: new BooleanMatcher(true),\n\thandler({ data }) {\n\t\treturn `the event '${data.name}' is unknown`\n\t}\n})\n\n// execute and get processing result\nconst result = eventRouter.exec({\n\tname: 'test1',\n})\n```\n\n### Matchers\n\nIn the core, matchers are responsible to decide if particular handler should be called or not. There is no magic: matchers are iterated on every request and first positive \"match\" calls defined handler.\n\n#### MethodMatcher ([source](./src/matchers/MethodMatcher.ts))\n\nMethod matcher is the simplest matcher and matches any of the passed http methods:\n\n```typescript\nrouter.addRoute({\n\tmatcher: new MethodMatcher(['OPTIONS', 'POST']),\n\t// method is either OPTIONS or POST\n\thandler: ({ match: { result: { method } } }) =\u003e `Method: ${method}`,\n})\n```\n\n#### ExactUrlPathnameMatcher ([source](./src/matchers/ExactUrlPathnameMatcher.ts))\n\nMatches given pathnames (but ignores query parameters):\n\n```typescript\nrouter.addRoute({\n\tmatcher: new ExactUrlPathnameMatcher(['/v1/graphql', '/v2/graphql']),\n\t// pathname is /v1/graphql or /v2/graphql\n\thandler: ({ match: { result: { pathname } } }) =\u003e `Path is ${pathname}`,\n})\n```\n\n#### ExactQueryMatcher ([source](./src/matchers/ExactQueryMatcher.ts))\n\nDefines expectations on query parameters:\n\n```typescript\nrouter.addRoute({\n\tmatcher: new ExactQueryMatcher({\n\t\t// example of 4 query parameters:\n\t\t// true defines mandatory parameters\n\t\tmustPresent: true,\n\t\t// false defines parameters expected to absent\n\t\tmustAbsent: false,\n\t\t// undefined defines optional parameters. They\n\t\t// aren't used for matching, but available as type\n\t\tisOptional: undefined,\n\t\t// array of strings defines expected parameter name and value\n\t\tmustExact: ['exactValue'] as const,\n\t}),\n\t// query parameter isOptional has type string | undefined\n\thandler: ({ match: { result: { query } } }) =\u003e query.isOptional,\n})\n```\n\n#### RegExpUrlMatcher ([source](./src/matchers/RegExpUrlMatcher.ts))\n\nAllows powerful expressions:\n\n```typescript\nrouter.addRoute({\n\tmatcher: new RegExpUrlMatcher\u003c{ userId: string }\u003e([/^\\/group\\/(?\u003cuserId\u003e[^/]+)$/]),\n\thandler: ({ match: { result: { match } } }) =\u003e `User id is: ${match.groups.userId}`,\n})\n```\nBe aware that regular expression must match the whole base url (also with query parameters) and not only `pathname`. Ordinal parameters can be used too.\n\n#### EndpointMatcher ([source](./src/matchers/EndpointMatcher.ts))\n\nEndpointMatcher is a combination of Method and RegExpUrl matcher for convenient usage:\n\n```typescript\nrouter.addRoute({\n\tmatcher: new EndpointMatcher\u003c{ userId: string }\u003e('GET', /^\\/group\\/(?\u003cuserId\u003e[^/]+)$/),\n\thandler: ({ match: { result: { method, match } } }) =\u003e `Group id ${match.groups.userId} matched with ${method} method`,\n})\n```\n\n### Middlewares\n\n**This whole section is highly experimental!**\n\nCurrently, there is no built-in API for middlewares. It seems like there is no aproach to provide centralized and typesafe way for middlewares. And it need some conceptual work, before it will be added. Open an issue, if you have a great idea!\n\n#### CorsMiddleware ([source](./src/middlewares/CorsMiddleware.ts))\n\nExample of CorsMiddleware usage:\n\n```typescript\nconst cors = CorsMiddleware(async () =\u003e {\n\treturn {\n\t\torigins: ['https://my-cool.site'],\n\t}\n})\n\nconst router = new NodeHttpRouter()\nrouter.addRoute({\n\tmatcher: new MethodMatcher(['OPTIONS', 'POST']),\n\t// use it\n\thandler: cors(({ match: { result: { method } } }) =\u003e `Method: ${method}.`),\n})\n```\n\nAvailable options:\n\n```typescript\ninterface CorsMiddlewareOptions {\n\t// exact origins like 'http://0.0.0.0:8080' or '*'\n\torigins: string[],\n\t// methods like 'POST', 'GET' etc.\n\tallowMethods?: HttpMethod[]\n\t// headers like 'Authorization' or 'X-Requested-With'\n\tallowHeaders?: string[]\n\t// allows cookies in CORS scenario\n\tallowCredentials?: boolean\n\t// max age in seconds\n\tmaxAge?: number\n}\n```\n\nSee ([source](./src/middlewares/CorsMiddleware.ts)) file for defaults.\n\n#### Create own middleware\n\n```typescript\n// example of a generic middleware, not a real cors middleware!\nfunction CorsMiddleware(origin: string) {\n\treturn function corsWrapper\u003c\n\t\tT extends MatchResult\u003cany\u003e,\n\t\tD extends {\n\t\t\t// add requirements of middleware\n\t\t\treq: ServerRequest,\n\t\t\tres: ServerResponse,\n\t\t}\n\t\u003e(\n\t\twrappedHandler: Handler\u003cT, D \u0026 {\n\t\t\t// new attributes can be used in the handler\n\t\t\tisCors: boolean\n\t\t}\u003e,\n\t): Handler\u003cT, D\u003e {\n\t\treturn async function corsHandler(params) {\n\t\t\tconst { req, res } = params.data\n\t\t\tconst isCors = !!req.headers.origin\n\t\t\t// -\u003e executed before handler\n\t\t\t// it's even possible to skip the handler at all\n\t\t\tconst result = await wrappedHandler({\n\t\t\t\t...params,\n\t\t\t\tdata: {\n\t\t\t\t\t...params.data,\n\t\t\t\t\tisCors,\n\t\t\t\t}\n\t\t\t})\n\t\t\t// -\u003e executed after handler, like:\n\t\t\tif (isCors) {\n\t\t\t\tres.setHeader('Access-Control-Allow-Origin', origin)\n\t\t\t}\n\t\t\treturn result\n\t\t}\n\t}\n}\n\n// create a configured instance of middleware\nconst cors = CorsMiddleware('http://0.0.0.0:8080')\n\nconst router = new NodeHttpRouter()\n\nrouter.addRoute({\n\tmatcher: new MethodMatcher(['OPTIONS', 'POST']),\n\t// use it\n\thandler: cors(({ match: { result: { method } }, data: { isCors } }) =\u003e `Method: ${method}. Cors: ${isCors}`),\n})\n```\n\n#### Combine middlewares\n\nOf course you can create a `middlewares` wrapper and put all middlewares inside it:\n```typescript\nfunction middlewares\u003c\n\tT extends MatchResultAny,\n\tD extends {\n\t\treq: ServerRequest\n\t\tres: ServerResponse\n\t}\n\u003e(\n\thandler: Handler\u003cT, D\n\t\t\u0026 MiddlewareData\u003ctypeof corsMiddleware\u003e\n\t\t\u0026 MiddlewareData\u003ctypeof sessionMiddleware\u003e\n\t\u003e,\n): Handler\u003cT, any\u003e {\n\treturn function middlewaresHandler(...args) {\n\t\treturn corsMiddleware(sessionMiddleware(handler))(...args)\n\t}\n}\n\nrouter.addRoute({\n\tmatcher,\n\t// use it\n\thandler: middlewares(({ data: { csrftoken } }) =\u003e `Token: ${csrftoken}`),\n})\n```\n\n### Nested routers\n\nThere are some use cases for nested routers:\n- Add features like multi-tenancy\n- Implement modularity\n- Apply middlewares globally\n\nAn example of multi-tenancy:\n\n```typescript\n// create main rooter\nconst rootRouter = new NodeHttpRouter()\n// attach some global urls\n// rootRouter.addRoute(...)\n\n// create a router used for all handlers\n// with tenant information\nconst tenantRouter = new Router\u003c{\n\treq: ServerRequest\n\tres: ServerResponse\n\ttenant: string\n}\u003e()\n\n// connect routers\nrootRouter.addRoute({\n\tmatcher: new RegExpUrlMatcher\u003c{\n\t\ttenant: string\n\t\turl: string\n\t}\u003e([/^\\/auth\\/realms\\/(?\u003ctenant\u003e[^/]+)(?\u003curl\u003e.+)/]),\n\thandler: ({ data, match }) =\u003e {\n\t\tconst { req, res } = data\n\t\t// figure tenant out\n\t\tconst { tenant, url } = match.result.match.groups\n\t\t// pass the new url down\n\t\treq.url = url\n\t\treturn tenantRouter.exec({\n\t\t\treq,\n\t\t\tres,\n\t\t\ttenant,\n\t\t})\n\t},\n})\n\n// attach some urls behind tenant\ntenantRouter.addRoute({\n\tmatcher: new ExactUrlPathnameMatcher(['/myurl']),\n\thandler: ({ data: { tenant }, match: { result: { pathname } } }) =\u003e {\n\t\t// if requested url is `/auth/realms/mytenant/myurl`, then:\n\t\t// tenant: mytenant\n\t\t// pathname: /myurl\n\t\treturn `tenant: ${tenant}, url: ${pathname}`\n\t}\n})\n```\n## Change log\n\nSee [releases](https://github.com/Bessonov/node-http-router/releases).\n\n## License\n\nMIT License\n\nCopyright (c) 2019 - today, Anton Bessonov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FBessonov%2Fnode-http-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FBessonov%2Fnode-http-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FBessonov%2Fnode-http-router/lists"}