Skip to content

Go Language Patterns

Build high-performance Go applications with Cursor IDE and Claude Code. This guide covers idiomatic Go patterns, concurrent programming with goroutines and channels, microservices development, testing strategies, and cloud-native deployment patterns.

  1. Initialize Go Project

    Terminal window
    # Standard Go project
    Ask: "Create a new Go module with standard project structure for a REST API"
    # Or use Agent mode for complete setup
    Agent: "Set up a Go microservice with Gin framework, PostgreSQL, and Docker"
  2. Configure Development Environment

    // .cursorrules or CLAUDE.md
    - Use Go 1.22+ features
    - Follow standard Go project layout
    - Use error wrapping with fmt.Errorf("%w", err)
    - Prefer table-driven tests
    - Use context for cancellation
    - Apply Go proverbs and idioms
  3. Set Up Dependencies

    Terminal window
    # go.mod
    module github.com/yourorg/yourproject
    go 1.22
    require (
    github.com/go-chi/chi/v5 v5.0.12
    github.com/jackc/pgx/v5 v5.5.5
    github.com/stretchr/testify v1.9.0
    go.uber.org/zap v1.27.0
    )
// AI Prompt
Agent: "Create a production-ready HTTP server with:
- Chi router with middleware
- Structured JSON logging
- Health checks
- Graceful shutdown
- OpenAPI documentation"
// Generated server
package main
import (
"context"
"net/http"
"os"
"os/signal"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
r := chi.NewRouter()
// Middleware
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
// Routes
r.Route("/api/v1", func(r chi.Router) {
r.Get("/health", healthHandler)
r.Mount("/users", userRoutes())
})
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
// Graceful shutdown
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
logger.Fatal("server failed", zap.Error(err))
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
logger.Fatal("shutdown failed", zap.Error(err))
}
}
// AI Prompt: "Implement service layer with dependency injection and error handling"
type UserService struct {
repo UserRepository
cache Cache
logger *zap.Logger
}
func NewUserService(repo UserRepository, cache Cache, logger *zap.Logger) *UserService {
return &UserService{
repo: repo,
cache: cache,
logger: logger,
}
}
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
// Check cache first
if user, err := s.cache.Get(ctx, "user:"+id); err == nil {
return user.(*User), nil
}
user, err := s.repo.FindByID(ctx, id)
if err != nil {
if errors.Is(err, ErrNotFound) {
return nil, fmt.Errorf("user %s not found: %w", id, err)
}
s.logger.Error("failed to fetch user",
zap.String("id", id),
zap.Error(err))
return nil, fmt.Errorf("fetch user: %w", err)
}
// Cache for next time
_ = s.cache.Set(ctx, "user:"+id, user, 5*time.Minute)
return user, nil
}
// AI Prompt
Ask: "Create a worker pool pattern with:
- Configurable number of workers
- Job queue with buffering
- Graceful shutdown
- Error handling
- Metrics collection"
type WorkerPool struct {
workers int
jobs chan Job
results chan Result
errors chan error
wg sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
metrics *Metrics
}
func (wp *WorkerPool) Start() {
for i := 0; i < wp.workers; i++ {
wp.wg.Add(1)
go wp.worker(i)
}
}
func (wp *WorkerPool) worker(id int) {
defer wp.wg.Done()
for {
select {
case job, ok := <-wp.jobs:
if !ok {
return
}
start := time.Now()
result, err := wp.processJob(job)
wp.metrics.RecordJobDuration(time.Since(start))
if err != nil {
wp.errors <- fmt.Errorf("worker %d: %w", id, err)
wp.metrics.IncrementErrors()
} else {
wp.results <- result
wp.metrics.IncrementSuccess()
}
case <-wp.ctx.Done():
return
}
}
}
// AI Prompt: "Implement context propagation with timeout and cancellation"
func (s *Service) ProcessBatch(ctx context.Context, items []Item) error {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
g, ctx := errgroup.WithContext(ctx)
// Process items concurrently
for _, item := range items {
item := item // capture loop variable
g.Go(func() error {
return s.processItem(ctx, item)
})
}
if err := g.Wait(); err != nil {
return fmt.Errorf("batch processing failed: %w", err)
}
return nil
}
func (s *Service) processItem(ctx context.Context, item Item) error {
// Check context before expensive operation
select {
case <-ctx.Done():
return ctx.Err()
default:
}
// Process with timeout
done := make(chan error, 1)
go func() {
done <- s.doExpensiveWork(item)
}()
select {
case err := <-done:
return err
case <-ctx.Done():
return fmt.Errorf("processing cancelled: %w", ctx.Err())
}
}
// AI Prompt
Agent: "Create a repository pattern with:
- Connection pooling
- Prepared statements
- Transaction support
- Query builders
- Migration support"
type PostgresRepository struct {
pool *pgxpool.Pool
}
func NewPostgresRepository(ctx context.Context, dsn string) (*PostgresRepository, error) {
config, err := pgxpool.ParseConfig(dsn)
if err != nil {
return nil, fmt.Errorf("parse config: %w", err)
}
config.MaxConns = 25
config.MinConns = 5
config.MaxConnLifetime = time.Hour
config.MaxConnIdleTime = time.Minute * 30
pool, err := pgxpool.NewWithConfig(ctx, config)
if err != nil {
return nil, fmt.Errorf("create pool: %w", err)
}
return &PostgresRepository{pool: pool}, nil
}
func (r *PostgresRepository) CreateUser(ctx context.Context, user *User) error {
query := `
INSERT INTO users (id, email, name, created_at)
VALUES ($1, $2, $3, $4)
ON CONFLICT (email) DO NOTHING
RETURNING id`
err := r.pool.QueryRow(ctx, query,
user.ID, user.Email, user.Name, user.CreatedAt,
).Scan(&user.ID)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrDuplicateEmail
}
return fmt.Errorf("insert user: %w", err)
}
return nil
}
// AI Prompt: "Implement transaction wrapper with rollback on error"
func (r *Repository) WithTransaction(ctx context.Context, fn func(pgx.Tx) error) error {
tx, err := r.pool.Begin(ctx)
if err != nil {
return fmt.Errorf("begin transaction: %w", err)
}
defer func() {
if p := recover(); p != nil {
_ = tx.Rollback(ctx)
panic(p) // re-throw panic
}
}()
if err := fn(tx); err != nil {
if rbErr := tx.Rollback(ctx); rbErr != nil {
return fmt.Errorf("rollback failed: %v, original error: %w", rbErr, err)
}
return err
}
if err := tx.Commit(ctx); err != nil {
return fmt.Errorf("commit transaction: %w", err)
}
return nil
}
// Usage
err := repo.WithTransaction(ctx, func(tx pgx.Tx) error {
if err := createOrder(ctx, tx, order); err != nil {
return err
}
return updateInventory(ctx, tx, order.Items)
})
// AI Prompt
Ask: "Generate comprehensive table-driven tests with:
- Edge cases
- Error scenarios
- Parallel execution
- Test fixtures
- Mocking"
func TestUserService_GetUser(t *testing.T) {
tests := []struct {
name string
userID string
mockSetup func(*mocks.UserRepository, *mocks.Cache)
want *User
wantErr error
}{
{
name: "success from cache",
userID: "123",
mockSetup: func(repo *mocks.UserRepository, cache *mocks.Cache) {
cache.On("Get", mock.Anything, "user:123").
Return(&User{ID: "123", Name: "John"}, nil)
},
want: &User{ID: "123", Name: "John"},
},
{
name: "success from database",
userID: "456",
mockSetup: func(repo *mocks.UserRepository, cache *mocks.Cache) {
cache.On("Get", mock.Anything, "user:456").
Return(nil, errors.New("not found"))
repo.On("FindByID", mock.Anything, "456").
Return(&User{ID: "456", Name: "Jane"}, nil)
cache.On("Set", mock.Anything, "user:456",
mock.Anything, 5*time.Minute).Return(nil)
},
want: &User{ID: "456", Name: "Jane"},
},
{
name: "user not found",
userID: "999",
mockSetup: func(repo *mocks.UserRepository, cache *mocks.Cache) {
cache.On("Get", mock.Anything, "user:999").
Return(nil, errors.New("not found"))
repo.On("FindByID", mock.Anything, "999").
Return(nil, ErrNotFound)
},
wantErr: ErrNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := new(mocks.UserRepository)
cache := new(mocks.Cache)
logger, _ := zap.NewDevelopment()
tt.mockSetup(repo, cache)
svc := NewUserService(repo, cache, logger)
got, err := svc.GetUser(context.Background(), tt.userID)
if tt.wantErr != nil {
assert.ErrorIs(t, err, tt.wantErr)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
repo.AssertExpectations(t)
cache.AssertExpectations(t)
})
}
}
// AI Prompt: "Create integration test with real database"
func TestIntegration_UserAPI(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
ctx := context.Background()
// Start PostgreSQL container
postgres, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Image: "postgres:16-alpine",
ExposedPorts: []string{"5432/tcp"},
Env: map[string]string{
"POSTGRES_PASSWORD": "test",
"POSTGRES_DB": "testdb",
},
WaitingFor: wait.ForListeningPort("5432/tcp"),
},
Started: true,
})
require.NoError(t, err)
defer postgres.Terminate(ctx)
// Get connection string
host, err := postgres.Host(ctx)
require.NoError(t, err)
port, err := postgres.MappedPort(ctx, "5432")
require.NoError(t, err)
dsn := fmt.Sprintf("postgres://postgres:test@%s:%s/testdb", host, port.Port())
// Run migrations
err = runMigrations(dsn)
require.NoError(t, err)
// Create test server
srv := setupTestServer(dsn)
// Test cases
t.Run("create user", func(t *testing.T) {
req := httptest.NewRequest("POST", "/api/v1/users",
strings.NewReader(`{"name":"Test User","email":"test@example.com"}`))
w := httptest.NewRecorder()
srv.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
})
}
// AI Prompt: "Implement error handling with wrapping and custom types"
type Error struct {
Code string
Message string
Err error
}
func (e *Error) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("%s: %s", e.Code, e.Message)
}
func (e *Error) Unwrap() error {
return e.Err
}
// Sentinel errors
var (
ErrNotFound = &Error{Code: "NOT_FOUND", Message: "resource not found"}
ErrUnauthorized = &Error{Code: "UNAUTHORIZED", Message: "unauthorized access"}
ErrValidation = &Error{Code: "VALIDATION", Message: "validation failed"}
)
// Error handling in HTTP handler
func handleError(w http.ResponseWriter, err error) {
var appErr *Error
if errors.As(err, &appErr) {
switch appErr.Code {
case "NOT_FOUND":
http.Error(w, appErr.Message, http.StatusNotFound)
case "UNAUTHORIZED":
http.Error(w, appErr.Message, http.StatusUnauthorized)
case "VALIDATION":
http.Error(w, appErr.Message, http.StatusBadRequest)
default:
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
return
}
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
// AI Prompt
Agent: "Create a gRPC service with:
- Protocol buffer definitions
- Server implementation
- Client with retry logic
- Interceptors for logging/auth
- Health checks"
// Generated proto service
syntax = "proto3";
package user.v1;
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}
// Server implementation
type server struct {
user.UnimplementedUserServiceServer
svc *UserService
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
user, err := s.svc.GetUser(ctx, req.Id)
if err != nil {
if errors.Is(err, ErrNotFound) {
return nil, status.Error(codes.NotFound, "user not found")
}
return nil, status.Error(codes.Internal, "internal error")
}
return &pb.User{
Id: user.ID,
Name: user.Name,
Email: user.Email,
}, nil
}
// AI Prompt: "Create benchmarks with memory allocation tracking"
func BenchmarkUserService_GetUser(b *testing.B) {
svc := setupBenchmarkService()
ctx := context.Background()
b.ResetTimer()
b.ReportAllocs()
b.Run("with cache", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = svc.GetUser(ctx, "cached-user")
}
})
b.Run("without cache", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = svc.GetUser(ctx, fmt.Sprintf("user-%d", i))
}
})
}
// CPU profiling
func BenchmarkConcurrentRequests(b *testing.B) {
if *cpuprofile != "" {
f, _ := os.Create(*cpuprofile)
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
processRequest()
}
})
}

Go Development Guidelines

  1. Simplicity - Write simple, clear code
  2. Interfaces - Accept interfaces, return structs
  3. Error Handling - Wrap errors with context
  4. Concurrency - Don’t communicate by sharing memory; share memory by communicating
  5. Testing - Table-driven tests with good coverage
  6. Documentation - Clear godoc comments
// AI: "Implement options pattern for configuration"
type Option func(*Server)
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func WithLogger(logger *zap.Logger) Option {
return func(s *Server) {
s.logger = logger
}
}
func NewServer(opts ...Option) *Server {
s := &Server{
port: 8080,
logger: zap.NewNop(),
}
for _, opt := range opts {
opt(s)
}
return s
}
// Usage
srv := NewServer(
WithPort(9090),
WithLogger(logger),
)
# AI Prompt: "Create optimized Docker build for Go application"
# Build stage
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache git ca-certificates
WORKDIR /build
# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download
# Build application
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o app ./cmd/server
# Final stage
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /build/app /app
EXPOSE 8080
ENTRYPOINT ["/app"]