{"id":22344491,"url":"https://github.com/ayonli/ngrpc","last_synced_at":"2025-07-30T03:31:05.515Z","repository":{"id":187328777,"uuid":"673939389","full_name":"ayonli/ngrpc","owner":"ayonli","description":"Make it easy to create clean and elegant gRPC based microservices.","archived":false,"fork":false,"pushed_at":"2024-08-04T17:54:25.000Z","size":803,"stargazers_count":4,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-29T18:02:48.630Z","etag":null,"topics":["golang","grpc","node-js"],"latest_commit_sha":null,"homepage":"","language":"Go","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/ayonli.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,"zenodo":null}},"created_at":"2023-08-02T19:10:40.000Z","updated_at":"2025-05-08T05:44:21.000Z","dependencies_parsed_at":"2025-04-25T20:42:42.361Z","dependency_job_id":null,"html_url":"https://github.com/ayonli/ngrpc","commit_stats":null,"previous_names":["hyurl/grpc-boot","ayonli/ngrpc"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/ayonli/ngrpc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ayonli%2Fngrpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ayonli%2Fngrpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ayonli%2Fngrpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ayonli%2Fngrpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ayonli","download_url":"https://codeload.github.com/ayonli/ngrpc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ayonli%2Fngrpc/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267803964,"owners_count":24146527,"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","status":"online","status_checked_at":"2025-07-30T02:00:09.044Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["golang","grpc","node-js"],"created_at":"2024-12-04T09:12:02.061Z","updated_at":"2025-07-30T03:31:05.204Z","avatar_url":"https://github.com/ayonli.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NgRPC\n\nMake it easy to create clean and elegant gRPC based microservices.\n\nThis package is written in and for both Node.js and Golang, enabling them to work seamlessly with\neach other in one codebase.\n\n*NOTE: this repo itself is an example of using NgRPC in practice.*\n\n## Getting Started\n\nFirst, install the CLI tool via one of the following commands:\n\n```sh\nnpm i -g @ayonli/ngrpc                              # for Node.js user\ngo install github.com/ayonli/ngrpc/cli/ngrpc@latest # for Golang user\n```\n\nThen use one of the following commands to initiate the project:\n\n```sh\nngrpc init -t node # for Node.js project\nngrpc init -t go   # for Golang project\n```\n\n## A Simple Example\n\nFirst, take a look at this configuration ([ngrpc.json](./ngrpc.json)):\n\n```json\n{\n    \"$schema\": \"https://raw.githubusercontent.com/ayonli/ngrpc/main/ngrpc.schema.json\",\n    \"protoPaths\": [\n        \"proto\"\n    ],\n    \"protoOptions\": {\n        \"defaults\": true\n    },\n    \"apps\": [\n        {\n            \"name\": \"example-server\",\n            \"url\": \"grpc://localhost:4000\",\n            \"serve\": true,\n            \"services\": [\n                \"services.ExampleService\"\n            ],\n            \"entry\": \"entry/main.ts\", // alternatively, we can use `entry/main.go` instead.\n            \"stdout\": \"out.log\"\n        },\n        {\n            \"name\": \"user-server\",\n            \"url\": \"grpcs://localhost:4001\",\n            \"serve\": true,\n            \"services\": [\n                \"services.UserService\"\n            ],\n            \"entry\": \"entry/main.go\",\n            \"stdout\": \"out.log\",\n            \"cert\": \"certs/cert.pem\",\n            \"key\": \"certs/cert.key\",\n            \"ca\": \"certs/ca.pem\"\n        },\n        {\n            \"name\": \"post-server\",\n            \"url\": \"grpcs://localhost:4002\",\n            \"serve\": true,\n            \"services\": [\n                \"services.PostService\"\n            ],\n            \"entry\": \"entry/main.ts\",\n            \"stdout\": \"out.log\",\n            \"cert\": \"certs/cert.pem\",\n            \"key\": \"certs/cert.key\",\n            \"ca\": \"certs/ca.pem\"\n        }\n    ]\n}\n```\n\nWe have two different entry files here, let's dig in each of them.\n\n[main.ts](./entry/main.ts)\n\n```ts\nimport ngrpc from \"@ayonli/ngrpc\";\n\nif (require.main?.filename === __filename) {\n    ngrpc.start(ngrpc.getAppName()).then(app =\u003e {\n        process.send?.(\"ready\"); // for PM2 compatibility\n        app.waitForExit();\n    }).catch(err =\u003e {\n        console.error(err);\n        process.exit(1);\n    });\n}\n```\n\n[main.go](./entry/main.go)\n\n```go\npackage main\n\nimport (\n    \"log\"\n\n    \"github.com/ayonli/ngrpc\"\n    _ \"github.com/ayonli/ngrpc/services\"\n)\n\nfunc main() {\n    app, err := ngrpc.Start(ngrpc.GetAppName())\n\n    if err != nil {\n        log.Fatal(err)\n    } else {\n        app.WaitForExit()\n    }\n}\n```\n\n### Explanation\n\n- `protoPaths` The directories that stores the `.proto` files. Normally, `.proto` files are stored\n    in the `proto` folder.\n\n    TIP: don't forget add `--proto_path=${workspaceRoot}/proto` to the `protoc.options` in VSCode's\n    settings in order for the **vscode-proto3** plugin to work properly.\n- `apps` This property configures the apps that this project serves and connects.\n    - `name` The name of the app.\n    - `url` The URL of the gRPC server, supported schemes are `grpc:`, `grpcs:`, `http:`, `https:`\n        or `xds:` (in Node.js, make sure package\n        [@grpc/grpc-js-xds](https://www.npmjs.com/package/@grpc/grpc-js-xds) is installed).\n    - `serve` If this app is served by the NgRPC app server. If this property is `false`, that\n        means the app is served by other programs and we just connect to it.\n    - `services` The services served by this app.\n    - `entry` The entry file used to spawn apps.\n        - During development, the entry filename shall be suffixed either by `.ts` or `.go`, when\n        running the program, NgRPC automatically compiles the file when needed.\n\n        - In production, the entry filename shall the compiled file's name, which is suffixed by `.js`\n        or has no suffix at all (for Golang, or `.exe` in Windows).\n\n        The program is spawned with the app name in the command line argument, we can use\n        `ngrpc.getAppName()` or `ngrpc.GetAppName()` to retrieve it.\n    - `stdout` Log file used for stdout.\n    \n    **More Options for `apps`**\n    \n    - `cert` The certificate filename when using TLS/SSL.\n    - `key` The private key filename when using TLS/SSL.\n    - `ca` The CA filename used to verify the other peer's certificates, when omitted, the system's\n        root CAs will be used.\n      \n        It's recommended that the gRPC application uses a non-public CA, so the client and the\n        server can establish a private connection that no outsiders can join in.\n    - `stderr` Log file used for stderr. If omitted and `stdout` is set, the program uses `stdout`\n        for `stderr` as well.\n    - `env` Additional environment variables passed to the `entry` file.\n\n**More Top Options**\n\n- `namespace` This is the root namespace of the services in Node.js, and the directory that\n    stores the service class (`.ts`) files. Normally, this option is omitted and use `services` by\n    default.\n- `importRoot` Where to begin searching for TypeScript / JavaScript files, the default is `.`. If\n    given, there are two rules for setting this option:\n    \n    - During development, if we use a source directory, say `src` (respectively, set\n        `compilerOptions.rootDir` to `src` in `tsconfig.json`), then this option should be set to\n        `src` as well.\n    - In production, if we compile our program to a build directory, say `dist` (respectively, set\n        `compilerOptions.outDir` to `dist` in `tsconfig.json`), then this option should be set to\n        `dist` as well.\n    \n    This option is also used for generating Golang code from the `.proto` files. If we set a `src`\n    directory, the code will be generated into that directory as well.\n- `protoOptions` These options are used when loading the `.proto` files in Node.js. Check\n    [ngrpc.schema.json](./ngrpc.schema.json) for more details.\n\nIn Node.js, services are automatically discoverd and imported when the program starts, in Golang, we\nimport the `services` package and name it `_` for its side-effect which registers the services.\n\nThen we use the `ngrpc.start()` / `ngrpc.Start()` function to initiate the app with the app name, it\ninitiates the server (if served) and client connections, prepares the services ready for use.\n\nNext we use the `app.waitForExit()` / `app.WaitForExit()` function to wait for the interrupt / exit\nsignal from the system for a graceful shutdown. In Golang, this function also keeps the program\nrunning and prevent premature exit.\n\nWith these simple configurations, we can write our gRPC application straightforwardly in `.proto`\nfiles and `.ts` or `.go` files, without any headache of when and how to start the server or\nconnect to the services, all is properly handled behind the scene.\n\n## CLI Commands\n\n- `ngrpc init [flags]` initiate a new NgRPC project\n    - `-t --template \u003cstring\u003e` available values are `go` or `node`\n\n    TIP: we can run this command twice with different template for the setup for both languages,\n    existing files will be untouched.\n- `ngrpc start [app]` start an app or all apps (exclude non-served ones)\n    - `app` the app name in the config file\n\n- `ngrpc restart [app]` restart an app or all apps (exclude non-served ones)\n    - `app` the app name in the config file\n\n- `ngrpc reload [app]` hot-reload an app or all apps\n    - `app` the app name in the config file\n\n    NOTE: only Node.js supports hot-reloading, Golang programs just reply that they don't support\n    this feature.\n- `ngrpc stop [app]` stop an app or all apps\n    - `app` the app name in the config file\n\n- `ngrpc list` or `ngrpc ls` list all apps (exclude non-served ones)\n\n- `ngrpc run \u003cfilename\u003e [args...]` runs a script file that attaches to the services, can be either\n    Golang (`.go`) or Node.js (`.ts`) programs.\n\n- `ngrpc protoc` generate golang program files from the proto files.\n\n    NOTE: this command is not used if our project only contains Node.js programs.\n\n- `ngrpc cert \u003cout\u003e [flags]` generate a pair of self-signed certificate.\n    - `--ca string` use a **ca.pem** for signing, if doesn't exist, it will be auto-generated\n        (default `certs/ca.pem`)\n    - `--caKey string` use a **ca.key** for signing, if doesn't exist, it will be auto-generated\n        (default `certs/ca.key`)\n\n- `ngrpc host [flags]` start the host server in standalone mode\n    - `--stop` stop the host server\n\n    NOTE: when `start` command is issued, the host server will be automatically started. The `host`\n    command is used when our program isn't started by the `start` command and we need the\n    functionalities that **NgRPC** provides. For examples, we start our program via **PM2** and we\n    still need the `reload` command to function once we deployed new updates.\n\n### Hot-Reloading (Node.js only)\n\nAfter we've modified our source code (or recompiled), the `.proto` files, or the config file, we can\nuse the `reload` command to hot-reload our apps without restarting the process.\n\nWhen the command is issued, the application will scan the imported service files and their\ndependencies (exclude the ones in `node_modules`), and reload them all at once. Since this procedure\ndoesn't restart the process, all context stores in the global scope are still available.\nHot-reloading is much faster than restarting the whole program, the client will experience\n0-downtime of our services.\n\nIt's important to point out, though, that the hot-reloading model this package uses only supports\nservices and their dependencies, any other code, for example, the change of the entry file, will not\njoin the reloading process and cannot be hot-reloaded, if such changes are made, a full restart is\nneeded for the new code to run.\n\n**Why not auto-reload when the file is changed?**\n\ngRPC uses the `.proto` file for definition and the `.ts` file for implementation, it's hard to keep\ntrack on both files at the same time. If reload immediately after a file is changed, there may be\ninconsistency between the two files and causing the program to fail. So this package provides the\n`reload` command that allows us to manually reload the app when we're done with our changes.\n\n### About Process Management\n\nThis package uses a host-guest model for process management. When using the `start` command to start\nthe app, the CLI tool also starts a host server to hold communication between apps, the host is\nresponsible to accept commands sent by the CLI tool and distribute them to the app.\n\nWhen an app crashes, the host server is also responsible for re-spawning it, this feature guarantees\nthat our app is always online. Except when the host server is running in standalone mode, in which\nthe app should be re-spawned by the external process management like PM2.\n\nMoreover, the CLI tool only works for the app instance, if the process contains other logics\nthat prevent the process to exit, the `stop` command will not be able to terminate the process, in\nsuch case, a force kill is required.\n\n## Implement a Service\n\nTo allow NgRPC to handle the serving and connecting process of our services, we need to\nimplement our services in a well-designed fashion.\n\nFor example, a typical service should be designed like this:\n\n### In Node.js\n\n```ts\nimport { ServiceClient, service } from \"@ayonli/ngrpc\";\n\ndeclare global {\n    namespace services {\n        const ExampleService: ServiceClient\u003cExampleService\u003e;\n    }\n}\n\n@service(\"services.ExampleService\")\nexport default class ExampleService {\n    // methods and private fields...\n}\n```\n\nIf this is a client-side service representation (only for referencing), it should be defined as an\nabstract class, like this:\n\n```ts\nimport { ServiceClient, service } from \"@ayonli/ngrpc\";\n\ndeclare global {\n    namespace services {\n        const UserService: ServiceClient\u003cUserService\u003e;\n    }\n}\n\n@service(\"github.ayonli.ngrpc.services.UserService\")\nexport default abstract class UserService {\n    // abstract methods...\n}\n```\n\n### In Golang\n\n```go\ntype ExampleService struct {\n    // A service to be served need to embed the UnimplementedServiceServer.\n    proto.UnimplementedExampleServiceServer\n}\n```\n\nFor NgRPC, a client-side service representation struct is needed as well:\n\n```go\n// A pure client service is an empty struct, which is only used for referencing to the service.\ntype ExampleService {}\n```\n\n#### func init\n\nIn each service file, we need to define an `init` function to use the service:\n\n```go\nfunc init() {\n    ngrpc.Use(\u0026ExampleService{})\n}\n```\n\n#### func Serve\n\nFor a service in order to be served, a `Serve()` method is required in the service struct:\n\n```go\nfunc (self *ExampleService) Serve(s grpc.ServiceRegistrar) {\n    proto.RegisterExampleServiceServer(s, self)\n\n    // other initiations, e.g. establishing database connections\n}\n```\n\n#### func Connect\n\nAll services (server-side and client-side) must implement the `Connect()` method in order to be\nconnected:\n\n```go\nfunc (self *Service) Connect(cc grpc.ClientConnInterface) proto.ExampleServiceClient {\n    return proto.NewExampleServiceClient(cc)\n}\n```\n\n#### func GetClient\n\nThe service may implement a `GetClient()` which can be used to reference the service client\nin a more expressive way:\n\n```go\nfunc (self *ExampleService) GetClient(route string) (proto.ExampleServiceClient, error) {\n    return ngrpc.GetServiceClient(self, route)\n}\n```\n\n## Lifecycle Support\n\n**In Node.js**\n\nSimply implement the `LifecycleSupportInterface` for the service class, for example:\n\n```ts\nimport { LifecycleSupportInterface, service } from \"@ayonli/ngrpc\";\n\n@service(\"services.ExampleService\")\nexport default class ExampleService implements LifecycleSupportInterface {\n    async init(): Promise\u003cvoid\u003e {\n        // When the service is loaded (or reloaded), the `init()` method will be automatically\n        // called, we can add some async logic inside it, for example, establishing database\n        // connection, which is normally impossible in the default `constructor()` method\n        // since it doesn't support asynchronous codes.\n    }\n\n    async destroy(): Promise\u003cvoid\u003e {\n        // When the app is about to stop, or the service is about to be reloaded, the `destroy()`\n        // method will be called, which gives the ability to clean up and release resources.\n    }\n}\n```\n\n**In Golang**\n\nWe use the `Serve()` method for additional setup, and the `Stop()` method for teardown.\n\n```go\nfunc (self *ExampleService) Serve(s grpc.ServiceRegistrar) {\n    proto.RegisterExampleServiceServer(s, self)\n\n    // other initiations, e.g. establishing database connections\n}\n\nfunc (self *ExampleService) Stop() {\n    // release database connections, etc.\n}\n```\n\n## Dependency Injection\n\n**In Node.js**\n\nJust add a private property in the class that points to another service, like this:\n\n```ts\n@service(\"github.ayonli.ngrpc.services.PostService\")\nexport default class PostService {\n    private userSrv = services.UserService;\n    // other private properties...\n\n    async getPost(query: PostQuery): Promise\u003cPost\u003e {\n        const post = this.postStore?.find(item =\u003e item.id === query.id);\n\n        if (post) {\n            // ---- highlight ----\n            const author = await this.userSrv.getUser({ id: post.author });\n            // ---- highlight ----\n\n            return { ...post, author };\n        } else {\n            throw new Error(`Post ${query.id} not found`);\n        }\n    }\n}\n```\n\n**In Golang**\n\nJust add an exported field that points to another service, like this:\n\n```go\ntype UserService struct {\n    proto.UnimplementedUserServiceServer\n    PostSrv   *PostService // set as exported field for dependency injection\n    // other non-exported fields...\n}\n\nfunc (self *UserService) GetMyPosts(ctx context.Context, query *proto.UserQuery) (*proto.PostQueryResult, error) {\n    return goext.Try(func() *services_proto.PostQueryResult {\n        user := goext.Ok(self.GetUser(ctx, query))\n\n        // ---- highlight ----\n        ins := goext.Ok(self.PostSrv.GetClient(user.Id))\n        res := goext.Ok(ins.SearchPosts(ctx, \u0026services_proto.PostsQuery{Author: \u0026user.Id}))\n        // ---- highlight ----\n\n        return (*services_proto.PostQueryResult)(res)\n    })\n}\n```\n\n## Load Balancing and Routing\n\nNgRPC comes with a client-side load balancer, we can set the same service in multiple apps and the\ninternal routing resolver which automatically redirect traffic for us.\n\nThere are three algorithms used based on the `route`:\n\n1. When `route` is not empty:\n    - If it matches one of the name or URL of the apps, the traffic is routed to that app directly.\n    - Otherwise the program hashes the route string against the apps and match one by the mod value\n        of `hash % active_nodes`.\n2. When `route` is empty, the program uses *round-robin* algorithm against the active nodes.\n\n**In Node.js**\n\nTo use this feature, define the request message that extends / augments the interface\n`RoutableMessageStruct`, it contains a `route` key that can be used in the internal client load\nbalancer. When the client sending a request which implements this interface, the program will\nautomatically route the traffic to a certain server evaluated by the `route` key.\n\n**In Golang**\n\nTo use this feature, we need to provide the `route` argument when calling the\n`ngrpc.GetServiceClient()` function or the `GetClient()` of the service struct.\n\nFor Example:\n\n**The `.proto` File**\n\n```proto\nmessage RequestMessage = {\n    string route = 1;\n    // other fields\n};\n```\n\n**In Node.js**\n\n```ts\n// the .ts file\nimport { RoutableMessageStruct } from \"@ayonli/ngrpc\";\n\nexport interface RequestMessage extends RoutableMessageStruct {\n    // other fields\n}\n```\n\n**In Golang**\n\n```go\nfunc main() {\n    msg := \u0026proto.RequestMessage{\n        Route: \"route key\"\n        // other fields\n    }\n    ins := ngrpc.GetServiceClient(\u0026services.SomeService{}, msg.Route)\n}\n```\n\nApart from the client-side load balancing, server-side load balancing is automatically supported by\ngRPC, either by reverse proxy like NGINX or using the `xds:` protocol for Envoy Proxy.\n\n## Unnamed App\n\nIt it possible to start an app without providing the name, such an app will not start the server, but\nonly connects to the services. This is useful when we're using gRPC services in a frontend server,\nfor example, a web server, which only handles client requests and direct calls to the backend gRPC\nservices, we need to establish connection between the web server and the RPC servers, but we don't\nwon't to serve any service in the web server.\n\nThe following apps do not serve, but connect to all the services according to the configuration file.\nWe can do all the stuffs provided by NgRPC in the web server as we would in the RPC server,\nbecause all the differences between the gRPC client and the gRPC server are hidden behind the scene.\n\n**In Node.js**\n\n```ts\nimport ngrpc from \"@ayonli/ngrpc\";\n\n(async () =\u003e {\n    const app = await ngrpc.start();\n})()\n```\n\n**In Golang**\n\n```go\nimport \"github.com/ayonli/ngrpc\"\n\nfunc main() {\n    app, err := ngrpc.Start(\"\")\n}\n```\n\n## 0-Services App\n\nApart from the unnamed app, an app can be configured with `serve: true` but no services, such an app\ndoes not actually start the gRPC server neither consume the port. But such an app can be used, say,\nto start a web server, which connects to the gRPC services and uses the facility this package\nprovides, such as the CLI tool and the hot-reloading model.\n\nFor example:\n\n```json\n// ngrpc.json\n{\n    // ...\n    \"apps\": [\n        {\n            \"name\": \"web-server\",\n            \"url\": \"http://localhost:3000\",\n            \"serve\": true,\n            \"services\": [], // leave this blank\n            // ...\n        },\n        // ...\n    ]\n}\n```\n\n**In Node.js**\n\nCheck out [web/main.ts](./web/main.ts).\n\n**In Golang**\n\nCheck out [web/main.go](./web/main.go).\n\n## Running Scripts\n\nSometimes it's just useful that we can write a simple script program that connects to the services\nand call their methods, for whatever the reason is, NgRPC makes this very easy for us.\n\n**In Node.js**\n\n```ts\nimport ngrpc from \"@ayonli/ngrpc\";\n\nngrpc.runSnippet(async () =\u003e {\n    const reply = await services.ExampleService.sayHello({ name: \"World\" });\n    console.log(reply.message);\n});\n```\n\n\n**In Golang**\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n\n    \"github.com/ayonli/goext\"\n    \"github.com/ayonli/ngrpc\"\n    \"github.com/ayonli/ngrpc-test/services\"\n    \"github.com/ayonli/ngrpc-test/services/proto\"\n)\n\nfunc main() {\n    done := ngrpc.ForSnippet()\n    defer done()\n\n    ctx := context.Background()\n    expSrv := goext.Ok((\u0026services.ExampleService{}).GetClient(\"\"))\n\n    reply := goext.Ok(expSrv.SayHello(ctx, \u0026proto.HelloRequest{Name: \"World\"}))\n    fmt.Println(reply.Message)\n}\n```\n\n## PM2 Integration\n\nIn production, instead of using the built-in process management feature, we can use PM2 to govern\nour program, which provides more features such as monitoring, log-rotation, web admin, etc. This\npackage is designed to work well with PM2, and not only Node.js, but the Golang program can be\nhosted by PM2, too.\n\nTo enable working with PM2, use the `ngrpc.loadConfigForPM2()` function in the `ecosystem.config.js`\n(or a custom filename), it loads the configurations and reorganizes them so that the same\nconfigurations can be used in PM2's configuration file. Moreover, it has done some work internally\nto support Golang programs for PM2, regardless of whether it has been compiled or not.\n\nFor example:\n\n```js\n// ecosystem.config.js\nconst { default: ngrpc } = require(\"@ayonli/ngrpc\");\n\nmodule.exports = ngrpc.loadConfigForPM2();\n```\n\n## Good Practices\n\nIn order to code a clean and elegant gRPC based application, apart from the features that NgRPC\nprovides, we can order our project by performing the following steps.\n\n1. Uses the `proto` folder to store all the `.proto` files in one place (by default).\n\n2. Uses the `services` folder for all the service files (by default), the namespace / package of\n    those files should be the same as the folder's name (which is also `services`).\n    \n    NOTE: although sub-folders and sub-namespaces / sub-packages are supported, it's a little tricky\n    in Golang, try to prevent this as we can.\n\n3. Design the `.proto` files with a reasonable scoped package name, don't just name it `services`, \n    instead, name it something like `[org].[repo].services`, `.proto` files should be shared and\n    reused across different projects, using a long name to prevent collision and provide useful\n    information about the service. Respectively, the directory path should reflect the package name.\n    See the [proto](./proto) files of this project as examples.\n\n4. **In Node.js**, use the same file structures and symbol names (as possible as we can) in the\n    class files to reflect the ones in the `.proto` files, create a consistent development\n    experience.\n\n    **In Golang**, Always implement the `GetClient()` method in the service and use an exported\n    field in the service struct to reference to each other (for dependency injection).\n\n\n## Programmatic API\n\nFor Node.js, see [api-node.md](./api-node.md).\n\nFor Golang, see [the package detail](https://pkg.go.dev/github.com/ayonli/ngrpc).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fayonli%2Fngrpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fayonli%2Fngrpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fayonli%2Fngrpc/lists"}