askill
go-patterns

go-patternsSafety 90Repository

Go backend patterns including chi router, gRPC, pgx, context handling, and concurrency.

0 stars
1.2k downloads
Updated 2/5/2026

Package Files

Loading files...
SKILL.md

Go Backend Patterns

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

Core Stack

ComponentLibraryPurpose
LanguageGo 1.22+Backend development
Buildgo build, go testCompilation and testing
Lintinggolangci-lintCode quality
HTTPchi routerRESTful APIs
RPCgrpc-goService communication
Databasepgx, sqlcPostgreSQL
Cacheredis-goCaching
Testingtestify, gomockTest framework and mocking
ObservabilityOpenTelemetryTracing 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

  1. golangci-lint always. Run linting before commits
  2. No naked returns. Always name return values in complex functions
  3. Error handling. Wrap errors with context, don't discard them
  4. Documentation. GoDoc comments on all exported items
  5. 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 < 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)

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

95/100Analyzed 2/12/2026

Comprehensive and highly actionable guide for Go backend development, covering core libraries, concurrency patterns, and testing best practices with clear code examples.

90
100
90
95
95

Metadata

Licenseunknown
Version-
Updated2/5/2026
Publishermajiayu000

Tags

apici-cddatabasegithublintingobservabilitytesting