{"id":15674955,"url":"https://github.com/cmorten/opine-http-proxy","last_synced_at":"2025-06-15T11:33:28.486Z","repository":{"id":45073943,"uuid":"268845542","full_name":"cmorten/opine-http-proxy","owner":"cmorten","description":"Proxy middleware for Deno Opine HTTP servers.","archived":false,"fork":false,"pushed_at":"2024-01-28T21:08:49.000Z","size":424,"stargazers_count":16,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-18T20:15:29.452Z","etag":null,"topics":["deno","deno-doc","deno-module","denojs","denoland","http-proxy","opine","opine-http-proxy","proxy-middleware"],"latest_commit_sha":null,"homepage":"https://cmorten.github.io/opine-http-proxy/","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/cmorten.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS.md","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["cmorten"]}},"created_at":"2020-06-02T15:58:16.000Z","updated_at":"2025-04-09T10:16:11.000Z","dependencies_parsed_at":"2024-08-04T00:17:53.040Z","dependency_job_id":null,"html_url":"https://github.com/cmorten/opine-http-proxy","commit_stats":null,"previous_names":["asos-craigmorten/opine-http-proxy"],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmorten%2Fopine-http-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmorten%2Fopine-http-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmorten%2Fopine-http-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmorten%2Fopine-http-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cmorten","download_url":"https://codeload.github.com/cmorten/opine-http-proxy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252787328,"owners_count":21804235,"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":["deno","deno-doc","deno-module","denojs","denoland","http-proxy","opine","opine-http-proxy","proxy-middleware"],"created_at":"2024-10-03T15:53:43.128Z","updated_at":"2025-05-06T23:32:31.245Z","avatar_url":"https://github.com/cmorten.png","language":"TypeScript","funding_links":["https://github.com/sponsors/cmorten"],"categories":["Modules"],"sub_categories":["Web framework"],"readme":"# opine-http-proxy\n\nProxy middleware for Deno Opine HTTP servers.\n\n[![GitHub tag](https://img.shields.io/github/tag/cmorten/opine-http-proxy)](https://github.com/cmorten/opine-http-proxy/tags/)\n![Test](https://github.com/cmorten/opine-http-proxy/workflows/Test/badge.svg)\n[![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/opineHttpProxy/mod.ts)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)\n[![GitHub issues](https://img.shields.io/github/issues/cmorten/opine-http-proxy)](https://img.shields.io/github/issues/cmorten/opine-http-proxy)\n![GitHub stars](https://img.shields.io/github/stars/cmorten/opine-http-proxy)\n![GitHub forks](https://img.shields.io/github/forks/cmorten/opine-http-proxy)\n![opine-http-proxy License](https://img.shields.io/github/license/cmorten/opine-http-proxy)\n[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/cmorten/opine-http-proxy/graphs/commit-activity)\n\n\u003cp align=\"left\"\u003e\n   \u003ca href=\"https://deno.land/x/opineHttpProxy\"\u003e\u003cimg src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Flatest-version%2Fx%2FopineHttpProxy%2Fmod.ts\" alt=\"opine-http-proxy latest /x/ version\" /\u003e\u003c/a\u003e\n   \u003ca href=\"https://github.com/denoland/deno/blob/main/Releases.md\"\u003e\u003cimg src=\"https://img.shields.io/badge/deno-^1.40.2-brightgreen?logo=deno\" alt=\"Minimum supported Deno version\" /\u003e\u003c/a\u003e\n   \u003ca href=\"https://deno-visualizer.danopia.net/dependencies-of/https/deno.land/x/opineHttpProxy/mod.ts\"\u003e\u003cimg src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Fdep-count%2Fx%2FopineHttpProxy%2Fmod.ts\" alt=\"opine-http-proxy dependency count\" /\u003e\u003c/a\u003e\n   \u003ca href=\"https://deno-visualizer.danopia.net/dependencies-of/https/deno.land/x/opineHttpProxy/mod.ts\"\u003e\u003cimg src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Fupdates%2Fx%2FopineHttpProxy%2Fmod.ts\" alt=\"opine-http-proxy dependency outdatedness\" /\u003e\u003c/a\u003e\n   \u003ca href=\"https://deno-visualizer.danopia.net/dependencies-of/https/deno.land/x/opineHttpProxy/mod.ts\"\u003e\u003cimg src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Fcache-size%2Fx%2FopineHttpProxy%2Fmod.ts\" alt=\"opine-http-proxy cached size\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n```ts\nimport { proxy } from \"https://deno.land/x/opineHttpProxy@3.2.0/mod.ts\";\nimport { opine } from \"https://deno.land/x/opine@2.3.1/mod.ts\";\n\nconst app = opine();\n\napp.use(proxy(\"https://github.com/cmorten/opine-http-proxy\"));\n\napp.listen(3000);\n```\n\n## Installation\n\nThis is a [Deno](https://deno.land/) module available to import direct from this\nrepo and via the [Deno Registry](https://deno.land/x).\n\nBefore importing, [download and install Deno](https://deno.land/#installation).\n\nYou can then import opine-http-proxy straight into your project:\n\n```ts\nimport { proxy } from \"https://deno.land/x/opineHttpProxy@3.2.0/mod.ts\";\n```\n\n## Docs\n\n- [opine-http-proxy Type Docs](https://cmorten.github.io/opine-http-proxy/)\n- [opine-http-proxy Deno Docs](https://doc.deno.land/https/deno.land/x/opineHttpProxy/mod.ts)\n- [License](https://github.com/cmorten/opine-http-proxy/blob/main/LICENSE.md)\n- [Changelog](https://github.com/cmorten/opine-http-proxy/blob/main/.github/CHANGELOG.md)\n\n## Usage\n\n### URL\n\nThe url argument that can be a string, URL or a function that returns a string\nor URL. This is used as the url to proxy requests to. The remaining path from a\nrequest that has not been matched by Opine will be appended to the provided url\nwhen making the proxied request.\n\n```ts\napp.get(\"/string\", proxy(\"http://google.com\"));\n\napp.get(\"/url\", proxy(new URL(\"http://google.com\")));\n\napp.get(\n  \"/function\",\n  proxy(() =\u003e new URL(\"http://google.com\")),\n);\n```\n\n### Streaming\n\nProxy requests and user responses are piped/streamed/chunked by default.\n\nIf you define a response modifier (`srcResDecorator`, `srcResHeaderDecorator`),\nor need to inspect the response before continuing (`filterRes`), streaming is\ndisabled, and the request and response are buffered. This can cause performance\nissues with large payloads.\n\n### Proxy Options\n\nYou can also provide several options which allow you to filter, customize and\ndecorate proxied requests and responses.\n\n```ts\napp.use(proxy(\"http://google.com\", proxyOptions));\n```\n\n#### filterReq(req, res) (supports Promises)\n\nThe `filterReq` option can be used to limit what requests are proxied.\n\nReturn false to continue to execute the proxy; return true to skip the proxy for\nthis request.\n\n```ts\napp.use(\n  \"/proxy\",\n  proxy(\"www.google.com\", {\n    filterReq: (req, res) =\u003e {\n      return req.method === \"GET\";\n    },\n  }),\n);\n```\n\nPromise form:\n\n```ts\napp.use(\n  proxy(\"localhost:12346\", {\n    filterReq: (req, res) =\u003e {\n      return new Promise((resolve) =\u003e {\n        resolve(req.method === \"GET\");\n      });\n    },\n  }),\n);\n```\n\nNote that in the previous example, `resolve(true)` will execute the happy path\nfor filter here (skipping the rest of the proxy, and calling `next()`).\n`reject()` will also skip the rest of proxy and call `next()`.\n\n#### srcResDecorator(req, res, proxyRes, proxyResData) (supports Promise)\n\nDecorate the inbound response object from the proxied request.\n\n```ts\napp.use(\n  \"/proxy\",\n  proxy(\"www.example.com\", {\n    srcResDecorator: (req, res, proxyRes, proxyResData) =\u003e {\n      data = JSON.parse(new TextDecoder().decode(proxyResData));\n      data.newProperty = \"exciting data\";\n\n      return JSON.stringify(data);\n    },\n  }),\n);\n```\n\n```ts\napp.use(\n  proxy(\"httpbin.org\", {\n    srcResDecorator: (req, res, proxyRes, proxyResData) =\u003e {\n      return new Promise((resolve) =\u003e {\n        proxyResData.message = \"Hello Deno!\";\n\n        setTimeout(() =\u003e {\n          resolve(proxyResData);\n        }, 200);\n      });\n    },\n  }),\n);\n```\n\n##### 304 - Not Modified\n\nWhen your proxied service returns 304 Not Modified this step will be skipped,\nsince there should be no body to decorate.\n\n##### Exploiting references\n\nThe intent is that this be used to modify the proxy response data only.\n\nNote: The other arguments are passed by reference, so you _can_ currently\nexploit this to modify either response's headers, for instance, but this is not\na reliable interface.\n\n#### memoizeUrl\n\nDefaults to `true`.\n\nWhen true, the `url` argument will be parsed on first request, and memoized for\nsubsequent requests.\n\nWhen `false`, `url` argument will be parsed on each request.\n\nFor example:\n\n```ts\nfunction coinToss() {\n  return Math.random() \u003e 0.5;\n}\n\nfunction getUrl() {\n  return coinToss() ? \"http://yahoo.com\" : \"http://google.com\";\n}\n\napp.use(\n  proxy(getUrl, {\n    memoizeUrl: false,\n  }),\n);\n```\n\nIn this example, when `memoizeUrl: false`, the coinToss occurs on each request,\nand each request could get either value.\n\nConversely, When `memoizeUrl: true`, the coinToss would occur on the first\nrequest, and all additional requests would return the value resolved on the\nfirst request.\n\n### srcResHeaderDecorator\n\nDecorate the inbound response headers from the proxied request.\n\n```ts\napp.use(\n  \"/proxy\",\n  proxy(\"www.google.com\", {\n    srcResHeaderDecorator(headers, req, res, proxyReq, proxyRes) {\n      return headers;\n    },\n  }),\n);\n```\n\n#### filterRes(proxyRes, proxyResData) (supports Promise form)\n\nAllows you to inspect the proxy response, and decide if you want to continue\nprocessing (via opine-http-proxy) or call `next()` to return control to Opine.\n\n```ts\napp.use(\n  \"/proxy\",\n  proxy(\"www.google.com\", {\n    filterRes(proxyRes) {\n      return proxyRes.status === 404;\n    },\n  }),\n);\n```\n\n### proxyErrorHandler\n\nBy default, `opine-http-proxy` will pass any errors except `ECONNRESET` and\n`ECONTIMEDOUT` to `next(err)`, so that your application can handle or react to\nthem, or just drop through to your default error handling.\n\nIf you would like to modify this behavior, you can provide your own\n`proxyErrorHandler`.\n\n```ts\n// Example of skipping all error handling.\n\napp.use(\n  proxy(\"localhost:12346\", {\n    proxyErrorHandler(err, res, next) {\n      next(err);\n    },\n  }),\n);\n\n// Example of rolling your own error handler\n\napp.use(\n  proxy(\"localhost:12346\", {\n    proxyErrorHandler(err, res, next) {\n      switch (err \u0026\u0026 err.code) {\n        case \"ECONNRESET\": {\n          return res.sendStatus(405);\n        }\n        case \"ECONNREFUSED\": {\n          return res.sendStatus(200);\n        }\n        default: {\n          next(err);\n        }\n      }\n    },\n  }),\n);\n```\n\n#### proxyReqUrlDecorator(url, req) (supports Promise form)\n\nDecorate the outbound proxied request url.\n\nThe returned url is used for the `fetch` method internally.\n\n```ts\napp.use(\n  \"/proxy\",\n  proxy(\"www.google.com\", {\n    proxyReqUrlDecorator(url, req) {\n      url.pathname = \"/\";\n\n      return url;\n    },\n  }),\n);\n```\n\nYou can also use Promises:\n\n```ts\napp.use(\n  \"/proxy\",\n  proxy(\"localhost:3000\", {\n    proxyReqUrlDecorator(url, req) {\n      return new Promise((resolve, reject) =\u003e {\n        if (url.pathname === \"/login\") {\n          url.port = 8080;\n        }\n\n        resolve(url);\n      });\n    },\n  }),\n);\n```\n\n#### proxyReqInitDecorator(proxyReqOpts, req) (supports Promise form)\n\nDecorate the outbound proxied request initialization options.\n\nThis configuration will be used within the `fetch` method internally to make the\nrequest to the provided url.\n\n```ts\napp.use(\n  \"/proxy\",\n  proxy(\"www.google.com\", {\n    proxyReqInitDecorator(proxyReqOpts, srcReq) {\n      // you can update headers\n      proxyReqOpts.headers.set(\"Content-Type\", \"text/html\");\n      // you can change the method\n      proxyReqOpts.method = \"GET\";\n\n      return proxyReqOpts;\n    },\n  }),\n);\n```\n\nYou can also use Promises:\n\n```ts\napp.use(\n  \"/proxy\",\n  proxy(\"www.google.com\", {\n    proxyReqInitDecorator(proxyReqOpts, srcReq) {\n      return new Promise((resolve, reject) =\u003e {\n        proxyReqOpts.headers.set(\"Content-Type\", \"text/html\");\n\n        resolve(proxyReqOpts);\n      });\n    },\n  }),\n);\n```\n\n#### secure\n\nNormally, your proxy request will be made on the same protocol as the `url`\nparameter. If you'd like to force the proxy request to be https, use this\noption.\n\n```ts\napp.use(\n  \"/proxy\",\n  proxy(\"http://www.google.com\", {\n    secure: true,\n  }),\n);\n```\n\nNote: if the proxy is passed a url without a protocol then HTTP will be used by\ndefault unless overridden by this option.\n\n#### preserveHostHeader\n\nYou can copy the host HTTP header to the proxied Opine server using the\n`preserveHostHeader` option.\n\n```ts\napp.use(\n  \"/proxy\",\n  proxy(\"www.google.com\", {\n    preserveHostHeader: true,\n  }),\n);\n```\n\n#### parseReqBody\n\nThe `parseReqBody` option allows you to control whether the request body should\nbe parsed and sent with the proxied request.\n\n#### reqAsBuffer\n\nConfigure whether the proxied request body should be sent as a UInt8Array\nbuffer.\n\nIgnored if `parseReqBody` is set to `false`.\n\n```ts\napp.use(\n  \"/proxy\",\n  proxy(\"www.google.com\", {\n    reqAsBuffer: true,\n  }),\n);\n```\n\n#### reqBodyEncoding\n\nThe request body encoding to use. Currently only \"utf-8\" is supported.\n\nIgnored if `parseReqBody` is set to `false`.\n\n```ts\napp.use(\n  \"/post\",\n  proxy(\"httpbin.org\", {\n    reqBodyEncoding: \"utf-8\",\n  }),\n);\n```\n\n#### timeout\n\nConfigure a timeout in ms for the outbound proxied request.\n\nIf not provided the request will never time out.\n\nTimed-out requests will respond with 504 status code and a X-Timeout-Reason\nheader.\n\n```ts\napp.use(\n  \"/\",\n  proxy(\"httpbin.org\", {\n    timeout: 2000, // in milliseconds, two seconds\n  }),\n);\n```\n\n## Contributing\n\n[Contributing guide](https://github.com/cmorten/opine-http-proxy/blob/main/.github/CONTRIBUTING.md)\n\n---\n\n## License\n\nopine-http-proxy is licensed under the [MIT License](./LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmorten%2Fopine-http-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcmorten%2Fopine-http-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmorten%2Fopine-http-proxy/lists"}