Golang Grpc
Samuel BertheCommunityCreated by samberDistributed by Samuel Berthe
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a comprehensive framework for building robust gRPC microservices in Go. It covers the full lifecycle of gRPC development, including protobuf organization, server and client implementation patterns, error handling, security configurations, and testing strategies. It emphasizes production-grade practices such as graceful shutdowns, interceptor usage, proper status code mapping, and connection management to ensure reliable and maintainable distributed systems.
What it does
- Provides best practices for organizing protobuf files and generating Go code.
- Offers patterns for implementing gRPC servers with health checks and interceptors.
- Defines standard approaches for client-side connection management and load balancing.
- Guides developers on mapping Go errors to appropriate gRPC status codes.
- Includes techniques for testing gRPC services using in-memory bufconn connections.
- Outlines security configurations including TLS/mTLS and production hardening.
When to use it
- When designing or implementing a new gRPC service in Go.
- When auditing existing gRPC codebases for correctness and operability.
- When debugging gRPC communication issues or performance bottlenecks.
- When setting up testing suites for gRPC clients and servers.
Advantages
- Promotes consistent, production-ready gRPC architecture.
- Reduces common pitfalls like connection leaks and improper error handling.
- Facilitates easier testing of complex gRPC stacks without network overhead.
- Encourages clean separation between transport layers and business logic.
Usage examples
- “How do I implement a graceful shutdown for my gRPC server in Go?”
- The agent provides a pattern using GracefulStop() with a timeout fallback to Stop() to ensure in-flight requests are handled correctly.
- “Show me how to test my gRPC service using bufconn.”
- The agent generates a test setup using bufconn to create an in-memory connection, allowing full stack testing without network dependencies.
- “What is the best way to handle errors in my gRPC service?”
- The agent explains how to use status.Errorf with specific codes like NotFound or InvalidArgument instead of returning raw errors.
- “How should I organize my proto files for a Go project?”
- The agent recommends a domain-based, versioned directory structure (e.g., proto/user/v1/) and the use of Request/Response wrapper messages.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/samber/golang-grpc/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip golang-grpc.zip -d .agents/skills
Documentation by samber
Persona: You are a Go distributed systems engineer. You design gRPC services for correctness and operability — proper status codes, deadlines, interceptors, and graceful shutdown matter as much as the happy path.
Modes:
Build mode — implementing a new gRPC server or client from scratch.
Review mode — auditing existing gRPC code for correctness, security, and operability issues.
Dependencies:
protoc:
brew install protobufprotoc-gen-go:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latestprotoc-gen-go-grpc:
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Go gRPC Best Practices
Treat gRPC as a pure transport layer — keep it separate from business logic. The official Go implementation is google.golang.org/grpc.
This skill is not exhaustive — refer to library documentation and code examples for more information:
For Go package docs, symbols, versions, importers, and known vulnerabilities, → See
samber/cc-skills-golang@golang-pkg-go-devskill (godig), preferred over Context7 for Go package facts.To navigate this library's usage in your own code (definitions, call sites, diagnostics), → See
samber/cc-skills-golang@golang-goplsskill (gopls).Context7 remains a fallback for docs not indexed on pkg.go.dev.
Quick Reference
| Concern | Package / Tool |
|---|---|
| Service definition | protoc or buf with .proto files |
| Code generation | protoc-gen-go, protoc-gen-go-grpc |
| Error handling | google.golang.org/grpc/status with codes |
| Rich error details | google.golang.org/genproto/googleapis/rpc/errdetails |
| Interceptors | grpc.ChainUnaryInterceptor, grpc.ChainStreamInterceptor |
| Middleware ecosystem | github.com/grpc-ecosystem/go-grpc-middleware |
| Testing | google.golang.org/grpc/test/bufconn |
| TLS / mTLS | google.golang.org/grpc/credentials |
| Health checks | google.golang.org/grpc/health |
Proto File Organization
Organize by domain with versioned directories (proto/user/v1/). Always use Request/Response wrapper messages — bare types like string cannot have fields added later. Generate with buf generate or protoc.
Proto & code generation reference
Server Implementation
Implement health check service (
grpc_health_v1) — Kubernetes probes need it to determine readinessUse interceptors for cross-cutting concerns (logging, auth, recovery) — keeps business logic clean
Use
GracefulStop()with a timeout fallback toStop()— drains in-flight RPCs while preventing hangsDisable reflection in production — it exposes your full API surface
srv := grpc.NewServer(
grpc.ChainUnaryInterceptor(loggingInterceptor, recoveryInterceptor),
)
pb.RegisterUserServiceServer(srv, svc)
healthpb.RegisterHealthServer(srv, health.NewServer())
go srv.Serve(lis)
// On shutdown signal:
stopped := make(chan struct{})
go func() { srv.GracefulStop(); close(stopped) }()
select {
case <-stopped:
case <-time.After(15 * time.Second):
srv.Stop()
}Interceptor Pattern
func loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("method=%s duration=%s code=%s", info.FullMethod, time.Since(start), status.Code(err))
return resp, err
}Client Implementation
Reuse connections — gRPC multiplexes RPCs on a single HTTP/2 connection; one-per-request wastes TCP/TLS handshakes
Set deadlines on every call (
context.WithTimeout) — without one, a slow upstream hangs goroutines indefinitelyUse
round_robinwith headless Kubernetes services viadns:///schemePass metadata (auth tokens, trace IDs) via
metadata.NewOutgoingContext
conn, err := grpc.NewClient("dns:///user-service:50051",
grpc.WithTransportCredentials(creds),
grpc.WithDefaultServiceConfig(`{
"loadBalancingPolicy": "round_robin",
"methodConfig": [{
"name": [{"service": ""}],
"timeout": "5s",
"retryPolicy": {
"maxAttempts": 3,
"initialBackoff": "0.1s",
"maxBackoff": "1s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE"]
}
}]
}`),
)
client := pb.NewUserServiceClient(conn)Error Handling
Always return gRPC errors using status.Error with a specific code — a raw error becomes codes.Unknown, telling the client nothing actionable. Clients use codes to decide retry vs fail-fast vs degrade.
| Code | When to Use |
|---|---|
InvalidArgument | Malformed input (missing field, bad format) |
NotFound | Entity does not exist |
AlreadyExists | Create failed, entity exists |
PermissionDenied | Caller lacks permission |
Unauthenticated | Missing or invalid token |
FailedPrecondition | System not in required state |
ResourceExhausted | Rate limit or quota exceeded |
Unavailable | Transient issue, safe to retry |
Internal | Unexpected bug |
DeadlineExceeded | Timeout |
// ✗ Bad — caller gets codes.Unknown, can't decide whether to retry
return nil, fmt.Errorf("user not found")
// ✓ Good — specific code lets clients act appropriately
if errors.Is(err, ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "user %q not found", req.UserId)
}
return nil, status.Errorf(codes.Internal, "lookup failed: %v", err)For field-level validation errors, attach errdetails.BadRequest via status.WithDetails.
Streaming
| Pattern | Use Case |
|---|---|
| Server streaming | Server sends a sequence (log tailing, result sets) |
| Client streaming | Client sends a sequence, server responds once (file upload, batch) |
| Bidirectional | Both send independently (chat, real-time sync) |
Prefer streaming over large single messages — avoids per-message size limits and lowers memory pressure.
func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
for _, u := range users {
if err := stream.Send(u); err != nil {
return err
}
}
return nil
}Testing
Use bufconn for in-memory connections that exercise the full gRPC stack (serialization, interceptors, metadata) without network overhead. Always test that error scenarios return the expected gRPC status codes.
Security
TLS MUST be enabled in production — credentials travel in metadata
For service-to-service auth, use mTLS or delegate to a service mesh (Istio, Linkerd)
For user auth, implement
credentials.PerRPCCredentialsand validate tokens in an auth interceptorReflection SHOULD be disabled in production to prevent API discovery
Performance
| Setting | Purpose | Typical Value |
|---|---|---|
keepalive.ServerParameters.Time | Ping interval for idle connections | 30s |
keepalive.ServerParameters.Timeout | Ping ack timeout | 10s |
grpc.MaxRecvMsgSize | Override 4 MB default for large payloads | 16 MB |
| Connection pooling | Multiple conns for high-load streaming | 4 connections |
Most services do not need connection pooling — profile before adding complexity.
Common Mistakes
| Mistake | Fix |
|---|---|
Returning raw error | Becomes codes.Unknown — client can't decide whether to retry. Use status.Errorf with a specific code |
| No deadline on client calls | Slow upstream hangs indefinitely. Always context.WithTimeout |
| New connection per request | Wastes TCP/TLS handshakes. Create once, reuse — HTTP/2 multiplexes RPCs |
| Reflection enabled in production | Lets attackers enumerate every method. Enable only in dev/staging |
codes.Internal for all errors | Wrong codes break client retry logic. Unavailable triggers retry; InvalidArgument does not |
| Bare types as RPC arguments | Can't add fields to string. Wrapper messages allow backwards-compatible evolution |
| Missing health check service | Kubernetes can't determine readiness, kills pods during deployments |
| Ignoring context cancellation | Long operations continue after caller gave up. Check ctx.Err() |
Cross-References
→ See
samber/cc-skills-golang@golang-contextskill for deadline and cancellation patterns→ See
samber/cc-skills-golang@golang-error-handlingskill for gRPC error to Go error mapping→ See
samber/cc-skills-golang@golang-observabilityskill for gRPC interceptors (logging, tracing, metrics)→ See
samber/cc-skills-golang@golang-testingskill for gRPC testing with bufconn
Files
- ATTRIBUTION.mdAdded by AutoGineer890 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md9.9 KiB
- evals/evals.json12.3 KiB
- references/protoc-reference.md4.6 KiB
- references/testing.md7.6 KiB
references/protoc-reference.md
Protobuf & Code Generation Reference
Table of Contents
Directory Layout
Proto File Conventions
go_packageconventions
Code Generation with
protocCommon
protocflags
Code Generation with
bufbuf.gen.yamlbuf.yamlCommon
bufcommands
Generated Code Patterns
Directory Layout
Organize proto files by domain with versioned directories. Always use Request/Response wrapper messages — bare types like string cannot have fields added later.
proto/
├── user/v1/
│ ├── user.proto # Messages
│ └── user_service.proto # Service RPCs
├── order/v1/
│ ├── order.proto
│ └── order_service.proto
└── shared/v1/
└── common.proto # Pagination, timestamps, shared enumsProto File Conventions
syntax = "proto3";
package mycompany.user.v1;
option go_package = "github.com/mycompany/myservice/gen/user/v1;userv1";
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
}
message GetUserRequest {
string user_id = 1;
}
message GetUserResponse {
User user = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
}go_package conventions
Format:
"import/path;alias"— the alias becomes the Go package nameConvention: lowercase version suffix (e.g.
userv1,orderv1)Generate into a
gen/directory to keep generated code separate from hand-written code
Code Generation with protoc
# Basic generation
protoc --go_out=gen --go_opt=paths=source_relative \
--go-grpc_out=gen --go-grpc_opt=paths=source_relative \
proto/user/v1/*.proto
# With validation (using buf-validate)
protoc --go_out=gen --go_opt=paths=source_relative \
--go-grpc_out=gen --go-grpc_opt=paths=source_relative \
--validate_out="lang=go:gen" \
proto/user/v1/*.proto
# Include external imports
protoc -I proto -I third_party \
--go_out=gen --go_opt=paths=source_relative \
--go-grpc_out=gen --go-grpc_opt=paths=source_relative \
proto/user/v1/*.protoCommon protoc flags
| Flag | Purpose |
|---|---|
--go_out=DIR | Output directory for message types |
--go-grpc_out=DIR | Output directory for service stubs |
--go_opt=paths=source_relative | Place output relative to proto source |
-I DIR | Add import path for proto dependencies |
--descriptor_set_out=FILE | Emit binary descriptor (for reflection) |
Code Generation with buf
buf is the recommended modern alternative to raw protoc. It manages dependencies, lints protos, and generates code from a single config.
buf.gen.yaml
version: v2
plugins:
- remote: buf.build/protocolbuffers/go
out: gen
opt: paths=source_relative
- remote: buf.build/grpc/go
out: gen
opt: paths=source_relativebuf.yaml
version: v2
modules:
- path: proto
lint:
use:
- STANDARD
breaking:
use:
- FILECommon buf commands
buf generate # Generate code from buf.gen.yaml
buf lint # Lint proto files
buf breaking --against '.git#branch=main' # Check backward compatibility
buf dep update # Update dependencies
buf build # Validate proto files compileGenerated Code Patterns
After generation, import and use the generated code:
import (
userv1 "github.com/mycompany/myservice/gen/user/v1"
)
// Server: implement the interface
type userServer struct {
userv1.UnimplementedUserServiceServer
}
// Client: use the generated client
client := userv1.NewUserServiceClient(conn)
resp, err := client.GetUser(ctx, &userv1.GetUserRequest{UserId: "123"})Always embed Unimplemented*Server (not Unsafe*Server) — it provides forward compatibility when new RPCs are added to the proto definition.
Requirements and compatibility
Written for: Claude Code, OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Declares allowed tools, Claude Code frontmatter
The author says: Designed for Claude Code, Codex or similar harness, and for projects using Golang.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- samber
- Publisher
- Samuel Berthe (Community)
- Distributed by
- Samuel Berthe
- Source
- samber/cc-skills-golang · skills/golang-grpc · commit bb1d43d
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by samber or Samuel Berthe.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download golang-grpc.zip