Skip to content

sa3akash/proto-package-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

6 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@sa3akash/proto

Protobuf + ConnectRPC definitions for Users and Posts services β€” with generated TypeScript and Go code, field-level validation via protovalidate, and automatic NPM publishing via GitHub Actions.

NPM Version CI License: MIT


πŸ“¦ Services

Service RPCs
UserService CreateUser Β· GetUser Β· UpdateUser Β· DeleteUser Β· ListUsers
PostService CreatePost Β· GetPost Β· UpdatePost Β· DeletePost Β· ListPosts

βœ… Validation Rules

All request fields carry protovalidate constraints encoded directly in the .proto files:

Field Rule
id fields UUID v4 format
name 2–100 chars Β· alphanumeric, spaces, _, -
email RFC 5322 email format
role / status Must be a defined enum value (UNSPECIFIED disallowed on create)
page >= 1
page_size 1 – 100
tags Max 20 items Β· each 1–50 chars
content Max 100,000 chars
Update/filter fields IGNORE_IF_ZERO_VALUE β€” constraint skipped when field is empty/zero

πŸ“ Project Structure

proto-package-example/
β”œβ”€β”€ protos/
β”‚   β”œβ”€β”€ users/v1/
β”‚   β”‚   └── users.proto              # package users.v1 β€” UserService + validation
β”‚   └── posts/v1/
β”‚       └── posts.proto              # package posts.v1 β€” PostService + validation
β”‚
β”œβ”€β”€ gen/                             # ⚠️ Generated β€” do not edit manually
β”‚   β”œβ”€β”€ ts/                          # TypeScript source (compiled β†’ dist/)
β”‚   β”‚   β”œβ”€β”€ users/v1/
β”‚   β”‚   β”‚   β”œβ”€β”€ users_pb.ts          # User messages & enums
β”‚   β”‚   β”‚   └── users_connect.ts     # UserService descriptor
β”‚   β”‚   β”œβ”€β”€ posts/v1/
β”‚   β”‚   β”‚   β”œβ”€β”€ posts_pb.ts          # Post messages & enums
β”‚   β”‚   β”‚   └── posts_connect.ts     # PostService descriptor
β”‚   β”‚   └── buf/validate/
β”‚   β”‚       └── validate_pb.ts       # protovalidate runtime types
β”‚   └── go/                          # Go packages (go get-able)
β”‚       β”œβ”€β”€ users/v1/
β”‚       β”‚   β”œβ”€β”€ users.pb.go          # package usersv1  β€” proto messages
β”‚       β”‚   └── usersv1connect/
β”‚       β”‚       └── users.connect.go # package usersv1connect β€” service interface & client
β”‚       β”œβ”€β”€ posts/v1/
β”‚       β”‚   β”œβ”€β”€ posts.pb.go          # package postsv1  β€” proto messages
β”‚       β”‚   └── postsv1connect/
β”‚       β”‚       └── posts.connect.go # package postsv1connect β€” service interface & client
β”‚       └── buf/validate/
β”‚           └── validate.pb.go       # protovalidate Go types
β”‚
β”œβ”€β”€ dist/                            # Built JS + .d.ts (published to npm)
β”‚   β”œβ”€β”€ users/v1/
β”‚   β”‚   β”œβ”€β”€ users_pb.js / .d.ts
β”‚   β”‚   └── users_connect.js / .d.ts
β”‚   └── posts/v1/
β”‚       β”œβ”€β”€ posts_pb.js / .d.ts
β”‚       └── posts_connect.js / .d.ts
β”‚
β”œβ”€β”€ buf.yaml                         # Buf workspace config (lint: STANDARD)
β”œβ”€β”€ buf.gen.yaml                     # Code generation plugins
β”œβ”€β”€ buf.lock                         # Locked buf dependency versions
β”œβ”€β”€ go.mod                           # Go module (github.com/sa3akash/proto-package-example)
β”œβ”€β”€ tsconfig.json                    # Compiles gen/ts β†’ dist
└── package.json                     # NPM package (@sa3akash/proto)

Why usersv1connect/ and postsv1connect/? protoc-gen-connect-go intentionally generates the ConnectRPC service interface, handler, and client into a separate Go package (usersv1connect) from the proto messages (usersv1). This prevents circular imports β€” your server implementations import messages from usersv1 and the service contract from usersv1connect.


🟦 TypeScript Usage

Install

npm install @sa3akash/proto @bufbuild/protobuf @connectrpc/connect

Import paths

Import Contents
@sa3akash/proto/users User, UserRole, all request/response types
@sa3akash/proto/users/connect UserService ConnectRPC descriptor
@sa3akash/proto/posts Post, PostStatus, all request/response types
@sa3akash/proto/posts/connect PostService ConnectRPC descriptor

Use generated types

import { User, UserRole } from "@sa3akash/proto/users";
import { Post, PostStatus } from "@sa3akash/proto/posts";

const user: User = {
  id: "550e8400-e29b-41d4-a716-446655440000",
  name: "Jane Doe",
  email: "jane@example.com",
  role: UserRole.USER,
};

ConnectRPC client (fetch / Node.js)

import { createClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-node";
import { UserService } from "@sa3akash/proto/users/connect";
import type { CreateUserRequest } from "@sa3akash/proto/users";
import { UserRole } from "@sa3akash/proto/users";

const transport = createConnectTransport({
  baseUrl: "https://api.example.com",
  httpVersion: "2",
});

const client = createClient(UserService, transport);

const res = await client.createUser({
  name: "Jane Doe",
  email: "jane@example.com",
  role: UserRole.USER,
});

console.log(res.user);

ConnectRPC server handler (Node.js)

import { ConnectRouter } from "@connectrpc/connect";
import { UserService } from "@sa3akash/proto/users/connect";
import type { CreateUserRequest, CreateUserResponse } from "@sa3akash/proto/users";

export function registerRoutes(router: ConnectRouter) {
  router.service(UserService, {
    async createUser(req): Promise<CreateUserResponse> {
      return {
        user: {
          id: crypto.randomUUID(),
          name: req.msg.name,
          email: req.msg.email,
          role: req.msg.role,
        },
      };
    },
  });
}

TypeScript validation (optional)

npm install @bufbuild/protovalidate
import { createValidator } from "@bufbuild/protovalidate";
import { CreateUserRequest } from "@sa3akash/proto/users";

const validator = await createValidator();

try {
  validator.validate(new CreateUserRequest({ name: "", email: "bad" }));
} catch (err) {
  console.error(err.violations); // field-level violation details
}

🟩 Go Usage

Install

go get github.com/sa3akash/proto-package-example

Package layout

Import path Package Contents
.../gen/go/users/v1 usersv1 Proto messages: User, CreateUserRequest, …
.../gen/go/users/v1/usersv1connect usersv1connect UserServiceClient, UserServiceHandler, NewUserServiceHandler
.../gen/go/posts/v1 postsv1 Proto messages: Post, CreatePostRequest, …
.../gen/go/posts/v1/postsv1connect postsv1connect PostServiceClient, PostServiceHandler, NewPostServiceHandler

ConnectRPC server

package main

import (
    "context"
    "net/http"

    "connectrpc.com/connect"
    "buf.build/go/protovalidate"

    proto "github.com/sa3akash/proto-package-example/gen/go"
    "github.com/sa3akash/proto-package-example/gen/go/usersv1connect"
)

type UserServer struct{}

func (s *UserServer) CreateUser(
    ctx context.Context,
    req *connect.Request[proto.CreateUserRequest],
) (*connect.Response[proto.CreateUserResponse], error) {
    // Validate request fields against protovalidate constraints
    v, _ := protovalidate.New()
    if err := v.Validate(req.Msg); err != nil {
        return nil, connect.NewError(connect.CodeInvalidArgument, err)
    }

    user := &proto.User{
        Id:    "generated-uuid",
        Name:  req.Msg.Name,
        Email: req.Msg.Email,
        Role:  req.Msg.Role,
    }
    return connect.NewResponse(&proto.CreateUserResponse{User: user}), nil
}

func main() {
    mux := http.NewServeMux()
    path, handler := usersv1connect.NewUserServiceHandler(&UserServer{})
    mux.Handle(path, handler)
    http.ListenAndServe(":8080", mux)
}

ConnectRPC client

import (
    "net/http"
    "connectrpc.com/connect"
    proto "github.com/sa3akash/proto-package-example/gen/go/users/v1"
    "github.com/sa3akash/proto-package-example/gen/go/users/v1/usersv1connect"
)

client := usersv1connect.NewUserServiceClient(
    http.DefaultClient,
    "https://api.example.com",
)

resp, err := client.GetUser(ctx, connect.NewRequest(&proto.GetUserRequest{
    Id: "550e8400-e29b-41d4-a716-446655440000",
}))
if err != nil {
    log.Fatal(err)
}
fmt.Println(resp.Msg.User)

πŸ”§ Local Development

Prerequisites

Generate TypeScript + Go code

# First time β€” fetch buf dependencies
bun run generate   # runs: buf generate
# or directly:
bunx buf dep update
bunx buf generate

Generated files appear in gen/ts/ and gen/go/.

Build TypeScript package

bun install
bun run build      # runs: tsc -p tsconfig.json

Verify Go module compiles

go mod tidy
go build ./...

πŸš€ Publishing to NPM

Push a version tag β€” GitHub Actions handles the rest:

git tag v1.0.1
git push origin v1.0.1

The publish.yml workflow will:

  1. Run buf generate (fresh TS + Go code)
  2. Build TypeScript (tsc)
  3. Stamp version from the git tag
  4. Publish @sa3akash/proto@1.0.1 to npmjs.com

Required GitHub Secret

Secret Description
NPM_TOKEN npm automation token with publish access

Create at: https://www.npmjs.com/settings/tokens

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors