golang-grpc
Production gRPC in Go: protobuf layout, codegen, interceptors, deadlines, error codes, streaming, health checks, TLS, and testing with bufconn
Works with
---
name: golang-grpc
description: Production gRPC in Go: protobuf layout, codegen, interceptors, deadlines, error codes, streaming, health checks, TLS, and testing with bufconn
license: MIT
---
# Go gRPC (Production)
## Overview
gRPC provides strongly-typed RPC APIs backed by Protocol Buffers, with first-class streaming support and excellent performance for service-to-service communication. This skill focuses on production defaults: versioned protos, deadlines, error codes, interceptors, health checks, TLS, and testability.
## Quick Start
### 1) Define a versioned protobuf API
✅ **Correct: versioned package**
```proto
// proto/users/v1/users.proto
syntax = "proto3";
package users.v1;
option go_package = "example.com/myapp/gen/users/v1;usersv1";
service UsersService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (stream User);
}
message GetUserRequest { string id = 1; }
message GetUserResponse { User user = 1; }
message ListUsersRequest { int32 page_size = 1; string page_token = 2; }
message User {
string id = 1;
string email = 2;
string display_name = 3;
}
```
❌ **Wrong: unversioned package (hard to evolve)**
```proto
package users;
```
### 2) Generate Go code
Install generators:
```bash
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
```
Generate:
```bash
protoc -I proto \
--go_out=./gen --go_opt=paths=source_relative \
--go-grpc_out=./gen --go-grpc_opt=paths=source_relative \
proto/users/v1/users.proto
```
### 3) Implement server with deadlines and status codes
✅ **Correct: validate + map errors to gRPC codes**
```go
package usersvc
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
usersv1 "example.com/myapp/gen/users/v1"
)
type Service struct {
usersv1.UnimplementedUsersServiceServer
Repo Repo
}
type Repo interface {
GetUser(ctx context.Context, id string) (User, error)
}
type User struct {
ID, Email, DisplayName string
}
func (s *Service) GetUser(ctx context.Context, req *usersv1.GetUserRequest) (*usersv1.GetUserResponse, error) {
if req.GetId() == "" {
return nil, status.Error(codes.InvalidArgument, "id is required")
}
u, err := s.Repo.GetUser(ctx, req.GetId())
if err != nil {
if err == ErrNotFound {
return nil, status.Error(codes.NotFound, "user not found")
}
return nil, status.Error(codes.Internal, "internal error")
}
return &usersv1.GetUserResponse{
User: &usersv1.User{
Id: u.ID,
Email: u.Email,
DisplayName: u.DisplayName,
},
}, nil
}
```
❌ **Wrong: return raw errors (clients lose code semantics)**
```go
return nil, errors.New("user not found")
```
## Core Concepts
### Deadlines and cancellation
Make every call bounded; enforce server-side timeouts for expensive handlers.
✅ **Correct: require deadline**
```go
if _, ok := ctx.Deadline(); !ok {
return nil, status.Error(codes.InvalidArgument, "deadline required")
}
```
### Metadata
Use metadata for auth/session correlation, not for primary request data.
✅ **Correct: read auth token from metadata**
```go
md, _ := metadata.FromIncomingContext(ctx)
auth := ""
if vals := md.Get("authorization"); len(vals) > 0 {
auth = vals[0]
}
```
## Interceptors (Middleware)
Use interceptors for cross-cutting concerns: auth, logging, metrics, tracing, request IDs.
✅ **Correct: unary interceptor with request ID**
```go
func unaryRequestID() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
id := uuid.NewString()
ctx = context.WithValue(ctx, requestIDKey{}, id)
resp, err := handler(ctx, req)
return resp, err
}
}
```
## Streaming patterns
### Server streaming (paginate or stream results)
✅ **Correct: stop on ctx.Done()**
```go
func (s *Service) ListUsers(req *usersv1.ListUsersRequest, stream usersv1.UsersService_ListUsersServer) error {
users, err := s.Repo.ListUsers(stream.Context(), int(req.GetPageSize()))
if err != nil {
return status.Error(codes.Internal, "internal error")
}
for _, u := range users {
select {
case <-stream.Context().Done():
return stream.Context().Err()
default:
}
if err := stream.Send(&usersv1.User{
Id: u.ID,
Email: u.Email,
DisplayName: u.DisplayName,
}); err != nil {
return err
}
}
return nil
}
```
### Unary vs streaming decision
- Use **unary** for single request/response and simple retries.
- Use **server streaming** for large result sets or continuous updates.
- Use **client streaming** for bulk uploads with one final response.
- Use **bidirectional streaming** for interactive protocols.
## Production Hardening
### Health checks and reflection
Add health service; enable reflection only in non-production environments.
✅ **Correct: health + conditional reflection**
```go
hs := health.NewServer()
grpc_health_v1.RegisterHealthServer(s, hs)
if env != "production" {
reflection.Register(s)
}
```
### Graceful shutdown
Prefer `GracefulStop` with a deadline.
✅ **Correct: graceful stop**
```go
stopped := make(chan struct{})
go func() {
grpcServer.GracefulStop()
close(stopped)
}()
select {
case <-stopped:
case <-time.After(10 * time.Second):
grpcServer.Stop()
}
```
### TLS
Use TLS (or mTLS) in production; avoid insecure credentials outside local dev.
✅ **Correct: server TLS**
```go
creds, err := credentials.NewServerTLSFromFile("server.crt", "server.key")
if err != nil { return err }
grpcServer := grpc.NewServer(grpc.Creds(creds))
```
## Testing (bufconn)
Test gRPC handlers without opening real sockets using `bufconn`.
✅ **Correct: in-memory gRPC test server**
```go
const bufSize = 1024 * 1024
lis := bufconn.Listen(bufSize)
srv := grpc.NewServer()
usersv1.RegisterUsersServiceServer(srv, &Service{Repo: repo})
go func() { _ = srv.Serve(lis) }()
ctx := context.Background()
conn, err := grpc.DialContext(
ctx,
"bufnet",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return lis.Dial() }),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil { t.Fatal(err) }
defer conn.Close()
client := usersv1.NewUsersServiceClient(conn)
resp, err := client.GetUser(ctx, &usersv1.GetUserRequest{Id: "1"})
_ = resp
_ = err
```
## Anti-Patterns
- **Ignore deadlines**: unbounded handlers cause tail latency and resource exhaustion.
- **Return string errors**: map domain errors to `codes.*` with `status.Error` or `status.Errorf`.
- **Stream without backpressure**: stop on `ctx.Done()` and handle `Send` errors.
- **Expose reflection in production**: treat reflection as a discovery surface.
## Troubleshooting
### Symptom: clients see `UNKNOWN` errors
Actions:
- Return `status.Error(codes.X, "...")` instead of raw errors.
- Wrap domain errors into typed errors, then map to gRPC codes.
### Symptom: slow/hanging requests
Actions:
- Require deadlines and propagate `ctx` to downstream calls.
- Add server-side timeouts and bounded concurrency in repositories.
### Symptom: flaky streaming
Actions:
- Stop streaming on `ctx.Done()` and handle `stream.Send` errors.
- Avoid buffering entire result sets before sending.
## Resources
- gRPC Go: https://github.com/grpc/grpc-go
- Protobuf Go: https://pkg.go.dev/google.golang.org/protobuf
- gRPC error codes: https://grpc.io/docs/guides/error/More Testing skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
agent-browser
vercel-labs/agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.
setup-pre-commit
mattpocock/skills
Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.

