Skip to main content

Overview

The microservices-app project uses buf generate to automatically generate code for both backend (Go) and frontend (TypeScript) from proto definitions.

buf.gen.yaml Configuration

The buf.gen.yaml file at the repository root configures code generation:
version: v2
clean: true
plugins:
  - remote: buf.build/protocolbuffers/go
    out: services/gen/go
    opt:
      - paths=source_relative
  - remote: buf.build/connectrpc/go
    out: services/gen/go
    opt:
      - paths=source_relative
  - remote: buf.build/bufbuild/es
    out: frontend/src/gen
    opt:
      - target=ts
  - remote: buf.build/connectrpc/query-es
    out: frontend/src/gen
    opt:
      - target=ts

Configuration Details

  • version: Uses buf v2 configuration format
  • clean: Removes existing generated files before regenerating (ensures clean output)
  • plugins: Defines four code generation plugins

Code Generation Plugins

Go Plugins (Backend)

1. protocolbuffers/go

Generates standard Go protobuf message types:
  • Output directory: services/gen/go/
  • Option: paths=source_relative - Generates files relative to proto file location
  • Creates: Message structs, serialization methods, getters

2. connectrpc/go

Generates Connect-RPC service stubs for Go:
  • Output directory: services/gen/go/
  • Option: paths=source_relative
  • Creates: Service interfaces, client implementations, HTTP handlers
  • Compatible with both gRPC and HTTP/JSON

TypeScript Plugins (Frontend)

3. bufbuild/es

Generates TypeScript message types using Protobuf-ES:
  • Output directory: frontend/src/gen/
  • Option: target=ts - Generates TypeScript (not JavaScript)
  • Creates: Message classes with type-safe builders

4. connectrpc/query-es

Generates connect-query hooks for React:
  • Output directory: frontend/src/gen/
  • Option: target=ts
  • Creates: React Query hooks for each RPC method
  • Integrates with TanStack Query for data fetching

Running Code Generation

Generate code from all proto files:
buf generate
This command:
  1. Cleans existing generated files (due to clean: true)
  2. Runs all four plugins in order
  3. Outputs to services/gen/go/ and frontend/src/gen/

Generated File Structure

Go Backend Output

services/gen/go/
├── greeter/v1/
│   ├── greeter.pb.go           # Protobuf messages
│   └── greeterconnect/
│       └── greeter.connect.go   # Connect-RPC service
├── caller/v1/
│   ├── caller.pb.go
│   └── callerconnect/
│       └── caller.connect.go
└── gateway/v1/
    ├── gateway.pb.go
    └── gatewayconnect/
        └── gateway.connect.go

TypeScript Frontend Output

frontend/src/gen/
├── greeter/v1/
│   ├── greeter_pb.ts           # Message types
│   └── greeter-GreeterService_connectquery.ts  # React Query hooks
├── caller/v1/
│   ├── caller_pb.ts
│   └── caller-CallerService_connectquery.ts
└── gateway/v1/
    ├── gateway_pb.ts
    └── gateway-GatewayService_connectquery.ts

Generated Code Examples

Go Service Interface

From proto/greeter/v1/greeter.proto:
service GreeterService {
  rpc Greet(GreetRequest) returns (GreetResponse) {}
}
Generates in services/gen/go/greeter/v1/greeterconnect/greeter.connect.go:
type GreeterServiceHandler interface {
    Greet(context.Context, *connect.Request[greeterv1.GreetRequest]) (*connect.Response[greeterv1.GreetResponse], error)
}

TypeScript React Query Hook

Generates in frontend/src/gen/greeter/v1/greeter-GreeterService_connectquery.ts:
export const useGreet = (options?: UseQueryOptions<GreetResponse>) => {
  return useQuery({
    ...options,
    queryKey: ['greeter.v1.GreeterService', 'Greet'],
    queryFn: () => greet(/* ... */)
  });
};
Usage in React component:
import { useGreet } from '@/gen/greeter/v1/greeter-GreeterService_connectquery';

function GreeterComponent() {
  const { data, isLoading } = useGreet({ name: 'World' });
  return <div>{data?.message}</div>;
}

Integration with Build Process

Development Workflow

  1. Edit proto files in proto/
  2. Run buf generate to regenerate code
  3. Use generated types in Go services and TypeScript frontend

Git Tracking

Generated files are tracked in git to ensure:
  • Consistent builds across environments
  • No dependency on buf during container builds
  • Clear diffs when proto definitions change

Pre-commit Hooks

The devenv pre-commit hooks do not automatically run buf generate. You must manually regenerate code after changing proto files.

CI Validation

GitHub Actions CI verifies that generated code is up-to-date:
buf generate --check
This fails if generated files don’t match the current proto definitions.

Ignoring Generated Code

Frontend Linting

Generated TypeScript code in frontend/src/gen/ is excluded from Biome linting:
// biome.json
{
  "files": {
    "ignore": ["src/gen/**"]
  }
}

Backend Linting

Generated Go code in services/gen/go/ is excluded from golangci-lint using build tags and path patterns.

Troubleshooting

Stale Generated Files

If you encounter import errors after updating proto files:
# Clean and regenerate
buf generate
The clean: true option automatically removes old files.

Plugin Version Issues

Buf automatically downloads plugins from the Buf Schema Registry. If you see version conflicts:
# Clear buf cache
rm -rf ~/.cache/buf
buf generate

Missing Dependencies

Ensure your environment includes buf:
# In devenv shell
which buf
buf --version
If not found, run direnv allow to reload the devenv environment.

Next Steps

Build docs developers (and LLMs) love