go-patterns

Production Go patterns for backend services with focus on concurrency, error handling, and idiomatic Go.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "go-patterns" with this command: npx skills add 5dlabs/cto/5dlabs-cto-go-patterns

Go Backend Patterns

Production Go patterns for backend services with focus on concurrency, error handling, and idiomatic Go.

Core Stack

Component Library Purpose

Language Go 1.22+ Backend development

Build go build, go test Compilation and testing

Linting golangci-lint Code quality

HTTP chi router RESTful APIs

RPC grpc-go Service communication

Database pgx, sqlc PostgreSQL

Cache redis-go Caching

Testing testify, gomock Test framework and mocking

Observability OpenTelemetry Tracing and metrics

Context7 Library IDs

Query these libraries for current best practices:

  • Chi Router: /go-chi/chi

  • pgx: /jackc/pgx

  • sqlc: /sqlc-dev/sqlc

  • testify: /stretchr/testify

  • OpenTelemetry Go: /open-telemetry/opentelemetry-go

Execution Rules

  • golangci-lint always. Run linting before commits

  • No naked returns. Always name return values in complex functions

  • Error handling. Wrap errors with context, don't discard them

  • Documentation. GoDoc comments on all exported items

  • Tests. Table-driven tests in _test.go files

Error Handling

Wrapping Errors with Context

import ( "fmt" "errors" )

func GetUser(id string) (*User, error) { user, err := db.FindUser(id) if err != nil { return nil, fmt.Errorf("get user %s: %w", id, err) } return user, nil }

Checking Specific Errors

if errors.Is(err, ErrNotFound) { return nil, status.Error(codes.NotFound, "user not found") }

Custom Error Types

type ValidationError struct { Field string Message string }

func (e *ValidationError) Error() string { return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message) }

Context Usage

Timeouts and Cancellation

func FetchData(ctx context.Context, url string) ([]byte, error) { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel()

req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
    return nil, fmt.Errorf("create request: %w", err)
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
    return nil, fmt.Errorf("execute request: %w", err)
}
defer resp.Body.Close()

return io.ReadAll(resp.Body)

}

Context Values (Use Sparingly)

type contextKey string

const userIDKey contextKey = "userID"

func WithUserID(ctx context.Context, userID string) context.Context { return context.WithValue(ctx, userIDKey, userID) }

func UserIDFromContext(ctx context.Context) (string, bool) { userID, ok := ctx.Value(userIDKey).(string) return userID, ok }

Concurrency Patterns

Graceful Goroutine Management

func worker(ctx context.Context, jobs <-chan Job) { for { select { case <-ctx.Done(): return case job, ok := <-jobs: if !ok { return } process(job) } } }

Worker Pool

func StartWorkerPool(ctx context.Context, jobs <-chan Job, numWorkers int) { var wg sync.WaitGroup

for i := 0; i &#x3C; numWorkers; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        worker(ctx, jobs)
    }()
}

wg.Wait()

}

Mutex for Shared State

type SafeCounter struct { mu sync.RWMutex count int }

func (c *SafeCounter) Increment() { c.mu.Lock() defer c.mu.Unlock() c.count++ }

func (c *SafeCounter) Value() int { c.mu.RLock() defer c.mu.RUnlock() return c.count }

Structured Logging

import ( "log/slog" "context" )

func ProcessRequest(ctx context.Context, id string) { logger := slog.With("request_id", id) logger.InfoContext(ctx, "processing request")

// On error
logger.ErrorContext(ctx, "failed to process",
    "error", err,
    "user_id", userID,
)

}

Chi Router Patterns

import "github.com/go-chi/chi/v5"

func NewRouter() chi.Router { r := chi.NewRouter()

r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(30 * time.Second))

r.Route("/api/v1", func(r chi.Router) {
    r.Get("/users/{id}", GetUser)
    r.Post("/users", CreateUser)
})

return r

}

Table-Driven Tests

func TestGetUser(t *testing.T) { tests := []struct { name string userID string want *User wantErr bool }{ { name: "valid user", userID: "123", want: &User{ID: "123", Name: "Test"}, }, { name: "invalid user", userID: "999", wantErr: true, }, }

for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
        got, err := GetUser(tt.userID)
        if (err != nil) != tt.wantErr {
            t.Errorf("GetUser() error = %v, wantErr %v", err, tt.wantErr)
            return
        }
        if !reflect.DeepEqual(got, tt.want) {
            t.Errorf("GetUser() = %v, want %v", got, tt.want)
        }
    })
}

}

Validation Commands

go fmt ./... go vet ./... golangci-lint run ./... go test ./... -race -v go build ./...

Guidelines

  • Keep functions small and focused

  • Use interfaces for abstraction

  • Handle errors explicitly, don't panic

  • Prefer composition over inheritance

  • Document exported functions and types

  • Use context for cancellation and timeouts

  • Leverage goroutines appropriately (not excessively)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

expo-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

better-auth-expo

No summary provided by upstream source.

Repository SourceNeeds Review
General

elysia-llm-docs

No summary provided by upstream source.

Repository SourceNeeds Review