{"id":29035968,"url":"https://github.com/tubitv/envoy-node","last_synced_at":"2025-06-26T12:36:30.983Z","repository":{"id":27886574,"uuid":"115315021","full_name":"Tubitv/envoy-node","owner":"Tubitv","description":"This is a boilerplate to help you adopt Envoy.","archived":false,"fork":false,"pushed_at":"2023-01-07T03:38:03.000Z","size":1558,"stargazers_count":34,"open_issues_count":23,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-15T07:32:22.894Z","etag":null,"topics":["envoy","grpc","http","nodejs"],"latest_commit_sha":null,"homepage":"https://tubitv.github.io/envoy-node/","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/Tubitv.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}},"created_at":"2017-12-25T07:05:03.000Z","updated_at":"2024-03-06T11:09:47.000Z","dependencies_parsed_at":"2023-01-14T07:40:56.711Z","dependency_job_id":null,"html_url":"https://github.com/Tubitv/envoy-node","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/Tubitv/envoy-node","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tubitv%2Fenvoy-node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tubitv%2Fenvoy-node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tubitv%2Fenvoy-node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tubitv%2Fenvoy-node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Tubitv","download_url":"https://codeload.github.com/Tubitv/envoy-node/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tubitv%2Fenvoy-node/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262068866,"owners_count":23253879,"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":["envoy","grpc","http","nodejs"],"created_at":"2025-06-26T12:36:30.433Z","updated_at":"2025-06-26T12:36:30.977Z","avatar_url":"https://github.com/Tubitv.png","language":"TypeScript","readme":"# Envoy Node\n\n[![Travis](https://api.travis-ci.org/Tubitv/envoy-node.svg?branch=master)](https://travis-ci.org/Tubitv/envoy-node) [![Coverage Status](https://coveralls.io/repos/github/Tubitv/envoy-node/badge.svg?branch=master)](https://coveralls.io/github/Tubitv/envoy-node?branch=master) [![npm version](https://img.shields.io/npm/v/envoy-node.svg)](https://www.npmjs.com/package/envoy-node)  ![npm license](https://img.shields.io/npm/l/envoy-node.svg)\n\nThis is a boilerplate to help you adopt [Envoy](https://github.com/envoyproxy/envoy).\n\nThere are multiple ways to config Envoy, one of the convenience way to mange different egress traffic is route the traffic by hostname (using [virtual hosts](https://www.envoyproxy.io/docs/envoy/v1.13.1/api-v2/rds.proto.html#virtualhost)). By doing so, you can use one egress port for all your egress dependencies:\n\n```yaml\nstatic_resources:\n  listeners:\n  - name: egress_listener\n    address:\n      socket_address:\n        address: 0.0.0.0\n        port_value: 12345\n    filter_chains:\n    - filters:\n      - name: envoy.http_connection_manager\n        typed_config:\n          \"@type\": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager\n          codec_type: AUTO\n          stat_prefix: ingress\n          use_remote_address: true\n          stat_prefix: http.test.egress\n          route_config:\n            name: egress_route_config\n            virtual_hosts:\n            - name: foo_service\n              domains:\n              - foo.service:8888  # Do not miss the port number here\n              routes:\n              - match:\n                  prefix: /\n                route:\n                  cluster: remote_foo_server\n            - name: bar_service\n              domains:\n              - bar.service:8888  # Do not miss the port number here\n              routes:\n              - match:\n                  prefix: /\n                route:\n                  cluster: remote_bar_server\n          http_filters:\n          - name: envoy.router\n            typed_config:\n              \"@type\": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router\n              dynamic_stats: true\n```\n\nBut it will bring you new problem, your code is becoming verbose:\n\n1. routing traffic to `127.0.0.1:12345` where egress port is listening\n2. setting host headers for each request\n3. propagating the tracing information\n\nAnd this library is going to help you deal with these things elegantly.\n\nFirst, let's tell the library where the egress port is binding. A recommended way is to set the information on the ingress header by [request_headers_to_add](https://www.envoyproxy.io/docs/envoy/v1.5.0/api-v2/rds.proto.html#envoy-api-field-routeconfiguration-request-headers-to-add):\n\n```yaml\nrequest_headers_to_add:\n- header:\n    key: x-tubi-envoy-egress-port\n    value: \"12345\"\n- header:\n    key: x-tubi-envoy-egress-addr\n    value: 127.0.0.1\n```\n\nYou can also set this by the constructor parameters of `EnvoyContext`.\n\n## High level APIs\n\n### HTTP\n\nFor HTTP, you can new the client like this:\n\n```js\nconst { EnvoyHttpClient, HttpRetryOn } = require(\"envoy-node\");\n\nasync function awesomeAPI(req, res) {\n  const client = new EnvoyHttpClient(req.headers);\n  const url = `http://foo.service:10080/path/to/rpc`\n  const request = {\n    message: \"ping\",\n  };\n  const optionalParams = {\n    // timeout 1 second\n    timeout: 1000,\n    // envoy will retry if server return HTTP 409 (for now)\n    retryOn: [HttpRetryOn.RETRIABLE_4XX],\n    // retry 3 times at most\n    maxRetries: 3,\n    // each retry will timeout in 300 ms\n    perTryTimeout: 300,\n    // any other headers you want to set\n    headers: {\n      \"x-extra-header-you-want\": \"value\",\n    },\n  };\n  const serializedJsonResponse = await client.post(url, request, optionalParams);\n  res.send({ serializedJsonResponse });\n  res.end();\n}\n```\n\n### gRPC\n\nFor gRPC, you can new the client like this:\n\n#### General RPC\n\n```js\nconst grpc = require(\"@grpc/grpc-js\");\nconst { envoyProtoDecorator, GrpcRetryOn } = require(\"envoy-node\");\n\nconst PROTO_PATH = __dirname + \"/ping.proto\";\nconst Ping = grpc.load(PROTO_PATH).test.Ping;\n\n// the original client will be decorated as a new class\nconst PingClient = envoyProtoDecorator(Ping);\n\nasync function awesomeAPI(call, callback) {\n  const client = new PingClient(\"bar.service:10081\", call.metadata);\n  const request = {\n    message: \"ping\",\n  };\n  const optionalParams = {\n    // timeout 1 second\n    timeout: 1000,\n    // envoy will retry if server return DEADLINE_EXCEEDED\n    retryOn: [GrpcRetryOn.DEADLINE_EXCEEDED],\n    // retry 3 times at most\n    maxRetries: 3,\n    // each retry will timeout in 300 ms\n    perTryTimeout: 300,\n    // any other headers you want to set\n    headers: {\n      \"x-extra-header-you-want\": \"value\",\n    },\n  };\n  const response = await client.pathToRpc(request, optionalParams);\n  callback(undefined, { remoteResponse: response });\n}\n```\n\n#### Streaming API\n\nBut they are also decorated to send the Envoy context. You can also specify the optional params (the last one) for features like `timeout` / `retryOn` / `maxRetries` / `perTryTimeout` provided by Envoy.\n\n**NOTE**:\n\n1. For streaming API, they are not implemented as `async` signature.\n2. The optional params (`timeout` etc.) is not tested and Envoy is not documented how it deal with streaming.\n\n##### Client streaming\n\n```js\nconst stream = innerClient.clientStream((err, response) =\u003e {\n  if (err) {\n    // error handling\n    return;\n  }\n  console.log(\"server responses:\", response);\n});\nstream.write({ message: \"ping\" });\nstream.write({ message: \"ping again\" });\nstream.end();\n```\n\n##### Sever streaming\n\n```js\nconst stream = innerClient.serverStream({ message: \"ping\" });\nstream.on(\"error\", error =\u003e {\n  // handle error here\n});\nstream.on(\"data\", (data: any) =\u003e {\n  console.log(\"server sent:\", data);\n});\nstream.on(\"end\", () =\u003e {\n  // ended\n});\n```\n\n##### Bidirectional streaming\n\n```js\nconst stream = innerClient.bidiStream();\nstream.write({ message: \"ping\" });\nstream.write({ message: \"ping again\" });\nstream.on(\"error\", error =\u003e {\n  // handle error here\n});\nstream.on(\"data\", (data: any) =\u003e {\n  console.log(\"sever sent:\", data);\n});\nstream.on(\"end\", () =\u003e {\n  stream.end();\n});\nstream.end();\n```\n\n## Low level APIs\n\nIf you want to have more control of your code, you can also use the low level APIs of this library:\n\n```js\nconst { envoyFetch, EnvoyContext, EnvoyHttpRequestParams, EnvoyGrpcRequestParams, envoyRequestParamsRefiner } = require(\"envoy-node\");\n\n// ...\n\nconst context = new EnvoyContext(\n  headerOrMetadata,\n  // specify port if we cannot indicate from\n  // - `x-tubi-envoy-egress-port` header or\n  // - environment variable ENVOY_DEFAULT_EGRESS_PORT\n  envoyEgressPort,\n  // specify address if we cannot indicate from\n  // - `x-tubi-envoy-egress-addr` header or\n  // - environment variable ENVOY_DEFAULT_EGRESS_ADDR\n  envoyEgressAddr\n);\n\n// for HTTP\nconst params = new EnvoyHttpRequestParams(context, optionalParams);\nenvoyFetch(params, url, init /* init like original node-fetch */)\n  .then(res =\u003e {\n    console.log(\"envoy tells:\", res.overloaded, res.upstreamServiceTime);\n    return res.json(); // or res.text(), just use it as what node-fetch returned\n  })\n  .then(/* ... */)\n\n// you are using request?\nconst yourOldRequestParams = {}; /* url or options */\nrequest(envoyRequestParamsRefiner(yourOldRequestParams, context /* or headers, grpc.Metadata */ ))\n\n// for gRPC\nconst client = new Ping((\n  `${context.envoyEgressAddr}:${context.envoyEgressPort}`, // envoy egress port\n  grpc.credentials.createInsecure()\n);\nconst requestMetadata = params.assembleRequestMeta()\nclient.pathToRpc(\n  request,\n  requestMetadata,\n  {\n    host: \"bar.service:10081\"\n  },\n  (error, response) =\u003e {\n    // ...\n  })\n\n```\n\nCheck out the [detail document](https://tubitv.github.io/envoy-node/) if needed.\n\n## Context store\n\nAre you finding it's too painful for you to propagate the context information through function calls' parameter?\n\nIf you are using Node.js V8, here is a solution for you:\n\n```javascript\nimport { envoyContextStore } from \"envoy-node\"; // import the store\n\nenvoyContextStore.enable(); // put this code when you application init\n\n// for each request, call this:\n  envoyContextStore.set(new EnvoyContext(req.headers));\n\n// for later get the request, simply:\n  envoyContextStore.get();\n```\n\n**IMPORTANT**\n\n1. according to the implementation, it's strictly requiring the `set` method is called exactly once per request. Or you will get incorrect context. Please check the document for more details. (TBD: We are working on a blog post for the details.)\n2. according to `asyn_hooks` implementation, [`destroy` is not called if the code is using HTTP keep alive](https://github.com/nodejs/node/issues/19859). Please use `setEliminateInterval` to set a time for deleting old context data or you may have memory leak. The default (5 mintues) is using if you don't set it.\n\n\n## For dev and test, or migrating to Envoy\n\nIf you are developing the application, you may probably do not have Envoy running. You may want to call the service directly:\n\nEither:\n\n```js\nnew EnvoyContext({\n  meta: grpcMetadata_Or_HttpHeader,\n\n  /**\n   * For dev or test environment, we usually don't have Envoy running. By setting directMode = true\n   * will make all the traffic being sent directly.\n   * If you set directMode to true, envoyManagedHosts will be ignored and set to an empty set.\n   */\n  directMode: true,\n\n  /**\n   * For easier migrate service to envoy step by step, we can route traffic to envoy for those service\n   * migrated. Fill this set for the migrated service.\n   * This field is default to `undefined` which means all traffic will be route to envoy.\n   * If this field is set to `undefined`, this library will also try to read it from `x-tubi-envoy-managed-host`.\n   * You can set in envoy config, like this:\n   *\n   * ``yaml\n   * request_headers_to_add:\n   * - key: x-tubi-envoy-managed-host\n   *   value: hostname:12345\n   * - key: x-tubi-envoy-managed-host\n   *   value: foo.bar:8080\n   * ``\n   *\n   * If you set this to be an empty set, then no traffic will be route to envoy.\n   */\n  envoyManagedHosts: new Set([\"some-hostname:8080\"]);\n\n})\n```\n\nor:\n\n```shell\nexport ENVOY_DIRECT_MODE=true # 1 works as well\n```\n\n## Contributing\n\nFor developing or running test of this library, you probably need to:\n\n1. have an envoy binary in your `PATH`, or:\n  ```shell\n  $ npm run download-envoy\n  $ export PATH=./node_modules/.bin/:$PATH\n  ```\n2. to commit your code change:\n  ```shell\n  $ git add . # or the things you want to commit\n  $ npm run commit # and answer the commit message accordingly\n  ```\n3. for each commit, the CI will auto release base on commit messages, to allow keeping the version align with Envoy, let's use fix instead of feature unless we want to upgrade minor version.\n\n## License\n\nMIT\n\n## Credits\n\n- this library is init by alexjoverm's [typescript-library-starter](https://github.com/alexjoverm/typescript-library-starter)\n\n- Thanks [@mattklein123](https://github.com/mattklein123) and Envoy community for questions and answers.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftubitv%2Fenvoy-node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftubitv%2Fenvoy-node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftubitv%2Fenvoy-node/lists"}