{"id":32118540,"url":"https://github.com/httpland/http-router","last_synced_at":"2026-02-23T04:32:24.196Z","repository":{"id":56065982,"uuid":"522607745","full_name":"httpland/http-router","owner":"httpland","description":"HTTP request router for standard Request and Response","archived":false,"fork":false,"pushed_at":"2023-03-02T14:16:12.000Z","size":252,"stargazers_count":6,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-07T16:00:30.887Z","etag":null,"topics":["handler","http","nested-routes","pattern-matching","request","response","router","routing","routing-table"],"latest_commit_sha":null,"homepage":"https://deno.land/x/http_router","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/httpland.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-08-08T15:33:59.000Z","updated_at":"2025-06-06T06:35:05.000Z","dependencies_parsed_at":"2022-08-15T12:31:04.479Z","dependency_job_id":null,"html_url":"https://github.com/httpland/http-router","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/httpland/http-router","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/httpland%2Fhttp-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/httpland%2Fhttp-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/httpland%2Fhttp-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/httpland%2Fhttp-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/httpland","download_url":"https://codeload.github.com/httpland/http-router/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/httpland%2Fhttp-router/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29738079,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-23T02:24:00.660Z","status":"ssl_error","status_checked_at":"2026-02-23T02:22:56.087Z","response_time":90,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["handler","http","nested-routes","pattern-matching","request","response","router","routing","routing-table"],"created_at":"2025-10-20T17:21:58.418Z","updated_at":"2026-02-23T04:32:24.182Z","avatar_url":"https://github.com/httpland.png","language":"TypeScript","readme":"\u003cdiv align=\"center\"\u003e\n\n# http-router\n\n\u003cimg src=\"https://raw.githubusercontent.com/httpland/http-router/main/_medias/logo.svg\" width=\"180px\" height=\"180px\" alt=\"logo\"\u003e\n\nHTTP request router for standard `Request` and `Response`.\n\n[![deno land](http://img.shields.io/badge/available%20on-deno.land/x-lightgrey.svg?logo=deno)](https://deno.land/x/http_router)\n[![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/http_router/mod.ts)\n[![GitHub release (latest by date)](https://img.shields.io/github/v/release/httpland/http-router)](https://github.com/httpland/http-router/releases)\n[![codecov](https://codecov.io/gh/httpland/http-router/branch/main/graph/badge.svg?token=nan4NUrx1V)](https://codecov.io/gh/httpland/http-router)\n[![GitHub](https://img.shields.io/github/license/httpland/http-router)](https://github.com/httpland/http-router/blob/main/LICENSE)\n\n[![test](https://github.com/httpland/http-router/actions/workflows/test.yaml/badge.svg)](https://github.com/httpland/http-router/actions/workflows/test.yaml)\n[![NPM](https://nodei.co/npm/@httpland/http-router.png?mini=true)](https://nodei.co/npm/@httpland/http-router/)\n\n\u003c/div\u003e\n\n---\n\n## Features\n\n- Based on\n  [URL pattern API](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API)\n- Web standard API compliant\n- Declarative\n- Functional programing pattern matching style\n- Automatically `HEAD` request handler\n- Nested route pathname\n- Tiny\n- Universal\n\n## Packages\n\nThe package supports multiple platforms.\n\n- deno.land/x - `https://deno.land/x/http_router/mod.ts`\n- npm - `@httpland/http-router`\n\n## URL router\n\n`URLRouter` provides routing between URLs and handlers.\n\nIt accepts the `URLPattern API` as is. This means that various url patterns can\nbe matched.\n\n```ts\nimport { URLRouter } from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\nimport { serve } from \"https://deno.land/std@$VERSION/http/mod.ts\";\n\nconst handler = URLRouter([\n  [{ pathname: \"/\" }, () =\u003e new Response(\"Home\")],\n  [\n    { password: \"admin\", pathname: \"/admin\" },\n    (request, context) =\u003e new Response(\"Hello admin!\"),\n  ],\n]);\n\nawait serve(handler);\n```\n\nIt accepts a set of `URLPatternInit` and handlers wrapped by `Iterable` object.\n\nIn other words, it is not limited to arrays.\n\n```ts\nimport { URLRouter } from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\n\nconst handler = URLRouter(\n  new Map([\n    [{ pathname: \"/\" }, () =\u003e new Response(\"Home\")],\n  ]),\n);\n```\n\n### Pathname routes\n\nURLPattern routes are the most expressive, but somewhat verbose. URL pattern\nmatching is usually done using `pathname`.\n\nURLRouter supports URL pattern matching with `pathname` as a first class.\n\n```ts\nimport { URLRouter } from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\n\nconst handler = URLRouter({\n  \"/api/students/:name\": (request, context) =\u003e {\n    const greeting = `Hello! ${context.params.name!}`;\n    return new Response(greeting);\n  },\n  \"/api/status\": () =\u003e new Response(\"OK\"),\n});\n```\n\nsame as:\n\n```ts\nimport { URLRouter } from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\n\nconst handler = URLRouter(\n  [\n    [\n      { pathname: \"/api/students/:name\" },\n      (request, context) =\u003e {\n        const greeting = `Hello! ${context.params.name!}`;\n        return new Response(greeting);\n      },\n    ],\n    [{ pathname: \"/api/status\" }, () =\u003e new Response(\"OK\")],\n  ],\n);\n```\n\n### URL Route handler context\n\nThe URL route handler receives the following context.\n\n| Name    | Description                                                                                             |\n| ------- | ------------------------------------------------------------------------------------------------------- |\n| pattern | `URLPattern`\u003cbr\u003eURL pattern.                                                                            |\n| result  | `URLPatternResult`\u003cbr\u003e Pattern matching result.                                                         |\n| params  | `URLPatternResult[\"pathname\"][\"groups\"]`\u003cbr\u003eURL matched parameters. Alias for `result.pathname.groups`. |\n\n### URL match pattern\n\nURL patterns can be defined using the\n[URL pattern API](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API).\n\n- Literal strings which will be matched exactly.\n- Wildcards (`/posts/*`) that match any character.\n- Named groups (`/books/:id`) which extract a part of the matched URL.\n- Non-capturing groups (`/books{/old}?`) which make parts of a pattern optional\n  or be matched multiple times.\n- RegExp groups (`/books/(\\\\d+)`) which make arbitrarily complex regex matches\n  with a few limitations.\n\n### Check routes validity\n\nThe router **never throws** an error. If the route is invalid, it will be\neliminated just.\n\nTo make sure that URLRoutes are valid in advance, you can use the validate\nfunction.\n\nFor example, `?` as pathname is an invalid pattern.\n\n```ts\nimport {\n  URLRouter,\n  URLRoutes,\n  validateURLRoutes,\n} from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\n\nconst routes: URLRoutes = {\n  \"?\": () =\u003e new Response(),\n};\nconst result = validateURLRoutes(routes);\n\nif (result !== true) {\n  // do something\n}\n\nconst handler = URLRouter(routes);\n```\n\nThe validate function returns `true` in case of success, or an object\nrepresenting the contents of the `Error` in case of failure.\n\nInvalid route means the following:\n\n- Invalid `URLPattern`\n- Duplicate `URLPattern`\n\nYou are completely free to do this or not.\n\n### Nested route pathname\n\n`nest` is nested URL pathname convertor. It provides a hierarchy of routing\ntables.\n\nHierarchical definitions are converted to flat definitions.\n\nYou can define a tree structure with a depth of 1. To nest more, combine it.\n\nExample of a routing table matching the following URL:\n\n- /\n- /api/v1/users\n- /api/v1/products\n- /api/v2/users\n- /api/v2/products\n\n```ts\nimport {\n  nest,\n  URLRouter,\n} from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\n\nconst routeHandler = () =\u003e new Response();\nconst v2 = nest(\"v2\", {\n  users: routeHandler,\n  products: routeHandler,\n});\nconst api = nest(\"/api\", {\n  ...nest(\"v1\", {\n    users: routeHandler,\n    products: routeHandler,\n  }),\n  ...v2,\n});\nconst handler = URLRouter({ ...api, \"/\": routeHandler });\n```\n\n#### Concatenate path segment\n\nPath segments are concatenated with slashes.\n\n```ts\nimport { nest } from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\nimport { assertEquals } from \"https://deno.land/std@$VERSION/testing/asserts.ts\";\n\nconst routeHandler = () =\u003e new Response();\nassertEquals(\n  nest(\"/api\", {\n    \"/hello\": routeHandler,\n    \"status/\": routeHandler,\n  }),\n  {\n    \"/api/hello\": routeHandler,\n    \"/api/status/\": routeHandler,\n  },\n);\n```\n\n#### Ambiguous pattern\n\nThe routing table defined in nest may have duplicate url patterns in some cases.\n\nAs seen in [Concatenate path segment](#concatenate-path-segment), segment\nslashes are safely handled. This results in the following definitions being\nidentical\n\n- branch\n- `/`branch\n\nThese are converted to the following pathname:\n\n`[root]/branch`\n\nIn this case, the routing table is ambiguous.\n\nRoute with the same pattern always take precedence **first** declared route.\n\nThis is because pattern matching is done from top to bottom.\n\n### Pattern matching performance\n\nPattern matching is done from top to bottom. The computational complexity is\nusually `O(n)`.\n\nPattern matching is done on URLs, so they are safely cached.\n\nAlready matched URL patterns have `O(1)` complexity.\n\n## HTTP request method router\n\n`MethodRouter` provides routing between HTTP request methods and handlers.\n\n```ts\nimport { MethodRouter } from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\nimport { serve } from \"https://deno.land/std@$VERSION/http/mod.ts\";\n\nconst handler = MethodRouter({\n  GET: () =\u003e new Response(\"From GET\"),\n  POST: async (request) =\u003e {\n    const data = await request.json();\n    return new Response(\"Received data!\");\n  },\n});\n\nawait serve(handler);\n```\n\n### HEAD request handler\n\nBy default, if a `GET` request handler is defined, a `HEAD` request handler is\nautomatically added.\n\nThis feature is based on RFC 9110, 9.1\n\n\u003e All general-purpose servers MUST support the methods GET and HEAD.\n\n```ts\nimport { MethodRouter } from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\nimport { serve } from \"https://deno.land/std@$VERSION/http/mod.ts\";\nimport { assertEquals } from \"https://deno.land/std@$VERSION/testing/asserts.ts\";\n\nconst handler = MethodRouter({\n  GET: () =\u003e {\n    const body = `Hello! world`;\n    return new Response(body, {\n      headers: {\n        \"content-length\": new Blob([body]).size.toString(),\n      },\n    });\n  },\n});\nconst request = new Request(\"http://localhost\", { method: \"HEAD\" });\nconst response = await handler(request);\n\nassertEquals(response.body, null);\nassertEquals(response.headers.get(\"content-length\"), \"12\");\n```\n\nThis can be disabled by setting `withHead` to `false`.\n\n```ts\nimport { MethodRouter } from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\n\nconst handler = MethodRouter({}, { withHead: false });\n```\n\n## Hook on matched handler\n\nThe router provides hooks for cross-cutting interests.\n\n## Before each\n\nProvides a hook to be called before the handler is invoked.\n\nYou can skip the actual handler call on a particular request by passing a\n`Response` object.\n\nThe handler call is skipped and the `afterEach` hook described below is called.\n\nExample of handling a preflight request that is of transversal interest:\n\n```ts\nimport { URLRouter } from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\nimport { assertEquals } from \"https://deno.land/std@$VERSION/testing/asserts.ts\";\nimport { preflightResponse } from \"https://deno.land/x/cors_protocol@$VERSION/mod.ts\";\n\nconst handler = URLRouter({\n  \"/\": () =\u003e new Response(),\n}, {\n  beforeEach: (request) =\u003e {\n    const preflightRes = preflightResponse(request, {});\n    return preflightRes;\n  },\n});\n```\n\n### After each\n\nProvides a hook that is called after each matching handler is called.\n\nWith this hook, you can monitor the handler's call and modify the resulting\nresponse.\n\nTo modify the response, a response object must be returned to the hook.\n\n```ts\nimport { URLRouter } from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\nimport { assertEquals } from \"https://deno.land/std@$VERSION/testing/asserts.ts\";\n\nconst handler = URLRouter({\n  \"/\": () =\u003e new Response(),\n}, {\n  afterEach: (response) =\u003e {\n    response.headers.set(\"x-router\", \"http-router\");\n    return response;\n  },\n});\n\nassertEquals(\n  (await handler(new Request(\"http://localhost\"))).headers.get(\"x-router\"),\n  \"http-router\",\n);\nassertEquals(\n  (await handler(\n    new Request(\"http://localhost/unknown\"),\n  )).headers.get(\"x-router\"),\n  null,\n);\n```\n\n## Detect error in router\n\nIf your defined handler throws an error internally, it will be supplemented and\nsafely return a `Response`.\n\nHere is the default response on error.\n\n```http\nHTTP/1.1 500 Internal Server Error\n```\n\n`onError` is called when an error is thrown internally by the handler. You may\ncustomize the error response.\n\n```ts\nimport { URLRouter } from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\n\nconst handler = URLRouter({\n  \"/\": () =\u003e {\n    throw Error(\"oops\");\n  },\n}, {\n  onError: (error) =\u003e {\n    console.error(error);\n    return new Response(\"Something wrong :(\", {\n      status: 500,\n    });\n  },\n});\n```\n\n## Spec\n\nIn addition to user-defined responses, routers may return the following\nresponses:\n\n| Status | Headers | Condition                                                   |\n| ------ | ------- | ----------------------------------------------------------- |\n| 404    |         | `URLRouter`\u003cbr\u003eIf not all url pattern match.                |\n| 405    | `allow` | `MethodRouter`\u003cbr\u003eIf HTTP method handler is not defined.    |\n| 500    |         | `URLRouter`, `MethodRouter`\u003cbr\u003eIf an internal error occurs. |\n\n## API\n\nAll APIs can be found in the\n[deno doc](https://doc.deno.land/https/deno.land/x/http_router/mod.ts).\n\n## Benchmark\n\nBenchmark script with comparison to several popular routers is available.\n\n```bash\ndeno task bench\n```\n\nBenchmark results can be found\n[here](https://github.com/httpland/http-router/actions/runs/3043238906/jobs/4902286626#step:4:60).\n\n## Related\n\nMore detailed references:\n\n- [Term definition](https://github.com/httpland/http-router/wiki/Term-definition)\n- [Developer's Guide](https://github.com/httpland/http-router/wiki/Developer's-Guide)\n\n### Recipes\n\n#### URLRouter + MethodRouter\n\nURLRouter and MethodRouter are independent, but will often be used together.\n\n```ts\nimport {\n  MethodRouter as $,\n  URLRouter,\n  URLRoutes,\n} from \"https://deno.land/x/http_router@$VERSION/mod.ts\";\n\nconst routeHandler = () =\u003e new Response();\nconst routes: URLRoutes = {\n  \"/\": $({\n    GET: routeHandler,\n  }),\n  \"/api/status/?\": routeHandler,\n  \"/api/users/:id/?\": (request, { params }) =\u003e {\n    // params.id!\n    return $({\n      POST: routeHandler,\n    })(request);\n  },\n};\nconst handler = URLRouter(routes);\n```\n\n#### Others\n\n- [router + compress](./_exmaples/../_examples/compress.ts)\n\n## License\n\nCopyright © 2022-present [httpland](https://github.com/httpland).\n\nReleased under the [MIT](./LICENSE) license\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhttpland%2Fhttp-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhttpland%2Fhttp-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhttpland%2Fhttp-router/lists"}