A protoc plugin and Go runtime that generates type-safe NATS clients and handlers from Protocol Buffer service definitions.
service OrderService {
rpc GetOrder(GetOrderRequest) returns (Order);
}// Client — one function call, no manual subjects or serialization
client := orders.NewOrderServiceClient(pn)
order, err := client.GetOrder(ctx, &orders.GetOrderRequest{OrderId: "abc"})
// Handler — implement an interface, register it
type handler struct { orders.UnimplementedOrderServiceHandler }
func (h *handler) GetOrder(ctx context.Context, req *orders.GetOrderRequest) (*orders.Order, error) {
return fetchOrder(ctx, req.OrderId)
}
reg, _ := orders.RegisterOrderServiceHandler(pn, &handler{})go install github.com/mudomi/protonats/cmd/protoc-gen-protonats@latestAdd the runtime library to your project:
go get github.com/mudomi/protonatsYou also need protoc and protoc-gen-go:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latestOr use the Docker image which bundles everything:
docker pull ghcr.io/mudomi/protonatssyntax = "proto3";
package myapp.orders;
option go_package = "github.com/myorg/myapp/gen/orders;orders";
import "protonats/options.proto";
service OrderService {
// Request/Reply (default pattern — no options needed)
rpc GetOrder(GetOrderRequest) returns (Order);
}
message GetOrderRequest {
string order_id = 1;
}
message Order {
string order_id = 1;
string name = 2;
}Copy proto/protonats/options.proto from this repo into your proto include path. This file defines the custom options ProtoNats uses.
With local tools:
protoc \
--go_out=gen --go_opt=paths=source_relative \
--protonats_out=gen --protonats_opt=paths=source_relative \
-I proto \
proto/myapp/orders/orders.protoOr with Docker (no local installs needed):
docker run --rm -v $(pwd):/work ghcr.io/mudomi/protonats \
--go_out=/work/gen --go_opt=paths=source_relative \
--protonats_out=/work/gen --protonats_opt=paths=source_relative \
-I /work/proto \
/work/proto/myapp/orders/orders.protoThis produces two files:
orders.pb.go— standard protobuf types (fromprotoc-gen-go)orders_protonats.pb.go— typed client, handler interface, and registration function
import (
"github.com/nats-io/nats.go"
"github.com/mudomi/protonats"
)
nc, _ := nats.Connect("nats://localhost:4222")
defer nc.Drain()
pn, _ := protonats.New(nc)type orderHandler struct {
orders.UnimplementedOrderServiceHandler
}
func (h *orderHandler) GetOrder(ctx context.Context, req *orders.GetOrderRequest) (*orders.Order, error) {
// Your business logic here
return &orders.Order{OrderId: req.OrderId, Name: "Widget"}, nil
}
reg, _ := orders.RegisterOrderServiceHandler(pn, &orderHandler{})
defer reg.Drain()client := orders.NewOrderServiceClient(pn)
order, err := client.GetOrder(ctx, &orders.GetOrderRequest{OrderId: "abc123"})| Pattern | Proto | Client returns |
|---|---|---|
| Request/Reply (default) | rpc Foo(Req) returns (Resp) |
(*Resp, error) |
| Publish (fire-and-forget) | rpc Foo(Msg) returns (Empty) + type: PUBLISH |
error |
| JetStream Publish | type: JETSTREAM_PUBLISH + stream: "NAME" |
error |
| JetStream Consume | type: JETSTREAM_CONSUME + stream: "NAME" |
handler-only |
Request/Reply is the default — no options needed.
Subjects are derived automatically from the proto package and method name:
{package}.{MethodName} → myapp.orders.GetOrder
Override with custom subjects, including dynamic segments:
rpc GetOrder(GetOrderRequest) returns (Order) {
option (protonats.method).subject = "orders.{order_id}";
}
message GetOrderRequest {
string order_id = 1 [(protonats.field).subject_token = true];
}Handlers return structured errors compatible with the NATS Service API:
return nil, protonats.Errorf(404, "order %s not found", req.OrderId)Clients receive them as *protonats.Error:
var pnErr *protonats.Error
if errors.As(err, &pnErr) {
log.Printf("code=%d msg=%s", pnErr.Code, pnErr.Message)
}- Design Overview — architecture, philosophy, patterns
- Proto Definition Guide — writing
.protofiles for ProtoNats - Go Library Guide — using generated code and the runtime
- Docker Usage — using the Docker image locally and in CI