# Golang Development

> Modern Go (Golang) development best practices (2024-2025). Use for project setup, modules, generics, concurrency, testing, HTTP/gRPC services, databases, CLI, linting, Docker, and production deployment.

- **Type:** Skill
- **Install:** `agentstack add skill-kinhluan-skills-golang-development`
- **Verified:** Pending review
- **Seller:** [kinhluan](https://agentstack.voostack.com/s/kinhluan)
- **Installs:** 0
- **Category:** [Cloud & Infrastructure](https://agentstack.voostack.com/c/cloud-infrastructure)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [kinhluan](https://github.com/kinhluan)
- **Source:** https://github.com/kinhluan/skills/tree/main/.agent-skills/golang-development

## Install

```sh
agentstack add skill-kinhluan-skills-golang-development
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Go Development

Modern Go development covering project structure, modules, generics, concurrency patterns, testing, web frameworks, gRPC, databases, CLI tools, linting, and production deployment. Targets Go 1.22+ with focus on 1.23+ and 1.24+ features.

> "Clear is better than clever." — The Go Proverbs

---

## 1. Project Setup & Modules

### Go Modules (`go.mod`)

Single source of truth for dependencies and module path.

```go
module github.com/example/myapp

go 1.23

require (
    github.com/gin-gonic/gin v1.10.0
    github.com/jackc/pgx/v5 v5.7.0
    google.golang.org/grpc v1.68.0
)

require (
    github.com/stretchr/testify v1.9.0 // indirect
)
```

**Key commands:**
```bash
go mod init github.com/example/myapp    # initialize module
go get github.com/gin-gonic/gin         # add dependency
go get -u ./...                         # update all dependencies
go mod tidy                             # remove unused, add missing
go mod download                         # download to module cache
go mod vendor                           # create vendor/ directory
go work init ./api ./worker             # Go workspace (multi-module)
```

### Standard Project Layout

```
myapp/
├── api/                    # API definitions (protobuf, OpenAPI)
│   └── proto/
├── cmd/                    # Application entry points
│   ├── api/
│   │   └── main.go
│   └── worker/
│       └── main.go
├── internal/               # Private application code
│   ├── domain/             # Business logic (entities, value objects)
│   ├── service/            # Use cases / application services
│   ├── repository/         # Data access layer
│   ├── handler/            # HTTP/gRPC handlers
│   └── config/             # Configuration
├── pkg/                    # Public library code (reusable)
│   └── utils/
├── migrations/             # Database migrations
├── scripts/                # Build and deployment scripts
├── configs/                # Configuration files
├── test/                   # Integration and e2e tests
├── go.mod
├── go.sum
├── Makefile
├── Dockerfile
└── README.md
```

**Rules:**
- `cmd//main.go` — one main per application
- `internal/` — cannot be imported by external modules
- `pkg/` — reusable packages, stable APIs

---

## 2. Go 1.22+ / 1.23+ / 1.24+ Features

### Go 1.22 — Enhanced For Loops

```go
// Loop variables are now per-iteration (no closure bugs)
funcs := []func(){}
for i := 0; i  b {  // ERROR: comparable doesn't support >
        return a
    }
    return b
}

// Correct: use constraints.Ordered
import "golang.org/x/exp/constraints"

func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// Generic struct
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(v T) {
    s.items = append(s.items, v)
}

func (s *Stack[T]) Pop() (T, bool) {
    var zero T
    if len(s.items) == 0 {
        return zero, false
    }
    v := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return v, true
}

// Type sets
 type Number interface {
     ~int | ~int64 | ~float64  // ~ allows underlying types
 }

func Sum[T Number](vals []T) T {
    var sum T
    for _, v := range vals {
        sum += v
    }
    return sum
}
```

### Interfaces & Embedding

```go
// Interface composition
 type Reader interface {
     Read(p []byte) (n int, err error)
 }

 type Writer interface {
     Write(p []byte) (n int, err error)
 }

 type ReadWriter interface {
     Reader
     Writer
 }

// Any (alias for interface{})
 func PrintAny(v any) {
     fmt.Printf("%T: %v\n", v, v)
 }

// Type assertions and switches
 func Describe(v any) string {
     switch val := v.(type) {
     case string:
         return "string: " + val
     case int:
         return fmt.Sprintf("int: %d", val)
     case fmt.Stringer:
         return val.String()
     default:
         return "unknown"
     }
 }
```

---

## 4. Error Handling

### Idiomatic Error Handling

```go
// Sentinel errors
 var ErrNotFound = errors.New("resource not found")
 var ErrInvalidInput = errors.New("invalid input")

// Error wrapping
 func LoadUser(id string) (*User, error) {
     user, err := db.QueryUser(id)
     if err != nil {
         return nil, fmt.Errorf("loading user %s: %w", id, err)
     }
     if user == nil {
         return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
     }
     return user, nil
 }

// Error checking
 user, err := LoadUser("123")
 if err != nil {
     if errors.Is(err, ErrNotFound) {
         http.Error(w, "Not found", http.StatusNotFound)
         return
     }
     log.Printf("unexpected error: %v", err)
     http.Error(w, "Internal error", http.StatusInternalServerError)
     return
 }

// Error type assertion
 var notFound *NotFoundError
 if errors.As(err, &notFound) {
     fmt.Printf("Resource type: %s, ID: %s\n", notFound.Resource, notFound.ID)
 }
```

### Custom Error Types

```go
 type NotFoundError struct {
     Resource string
     ID       string
 }

 func (e *NotFoundError) Error() string {
     return fmt.Sprintf("%s %s not found", e.Resource, e.ID)
 }

// Validation errors with multiple fields
 type ValidationError struct {
     Field   string
     Message string
 }

 type ValidationErrors []ValidationError

 func (v ValidationErrors) Error() string {
     var msgs []string
     for _, e := range v {
         msgs = append(msgs, fmt.Sprintf("%s: %s", e.Field, e.Message))
     }
     return "validation failed: " + strings.Join(msgs, "; ")
 }
```

---

## 5. Concurrency

### Goroutines & Channels

```go
// Basic goroutine
 go func() {
     fmt.Println("running concurrently")
 }()

// Buffered channel
 ch := make(chan int, 10)
 go func() {
     for i := range 5 {
         ch <- i
     }
     close(ch)
 }()

 for v := range ch {
     fmt.Println(v)
 }

// Select for multiplexing
 select {
 case v := <-ch1:
     fmt.Println("from ch1:", v)
 case v := <-ch2:
     fmt.Println("from ch2:", v)
 case <-time.After(5 * time.Second):
     fmt.Println("timeout")
 default:
     fmt.Println("no channel ready")
 }
```

### Context — Request Scoping & Cancellation

```go
// Create context with timeout
 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 defer cancel()

// Pass through call chain
 func ProcessRequest(ctx context.Context, req Request) (*Response, error) {
     // Check cancellation
     select {
     case <-ctx.Done():
         return nil, ctx.Err()  // context.DeadlineExceeded or context.Canceled
     default:
     }

     // Pass to downstream
     user, err := userService.Get(ctx, req.UserID)
     if err != nil {
         return nil, err
     }
     // ...
 }

// Context with values (request-scoped metadata)
 type contextKey string
 const requestIDKey contextKey = "request-id"

 func WithRequestID(ctx context.Context, id string) context.Context {
     return context.WithValue(ctx, requestIDKey, id)
 }

 func RequestIDFrom(ctx context.Context) string {
     if id, ok := ctx.Value(requestIDKey).(string); ok {
         return id
     }
     return ""
 }
```

### sync Package Patterns

```go
// WaitGroup for goroutine synchronization
 var wg sync.WaitGroup
 for i := range 3 {
     wg.Add(1)
     go func(id int) {
         defer wg.Done()
         fmt.Printf("worker %d done\n", id)
     }(i)
 }
 wg.Wait()

// Mutex for shared state
 type Counter struct {
     mu    sync.Mutex
     count int
 }

 func (c *Counter) Inc() {
     c.mu.Lock()
     defer c.mu.Unlock()
     c.count++
 }

// RWMutex for read-heavy workloads
 type Cache struct {
     mu    sync.RWMutex
     data  map[string]string
 }

 func (c *Cache) Get(key string) (string, bool) {
     c.mu.RLock()
     defer c.mu.RUnlock()
     v, ok := c.data[key]
     return v, ok
 }

 func (c *Cache) Set(key, value string) {
     c.mu.Lock()
     defer c.mu.Unlock()
     c.data[key] = value
 }

// sync.Once for one-time initialization
 var (
     db     *sql.DB
     dbOnce sync.Once
 )

 func GetDB() *sql.DB {
     dbOnce.Do(func() {
         var err error
         db, err = sql.Open("postgres", dsn)
         if err != nil {
             log.Fatal(err)
         }
     })
     return db
 }

// sync.Map for concurrent map (rarely needed, prefer RWMutex)
 var m sync.Map
 m.Store("key", "value")
 if v, ok := m.Load("key"); ok {
     fmt.Println(v)
 }
```

### errgroup — Concurrent Error Handling

```go
 import "golang.org/x/sync/errgroup"

 func ProcessBatch(ctx context.Context, items []Item) error {
     g, ctx := errgroup.WithContext(ctx)
     g.SetLimit(10)  // max concurrent

     for _, item := range items {
         item := item  // capture loop variable (pre-1.22)
         g.Go(func() error {
             return processItem(ctx, item)
         })
     }

     if err := g.Wait(); err != nil {
         return fmt.Errorf("batch processing: %w", err)
     }
     return nil
 }
```

### Worker Pools

```go
 func WorkerPool(ctx context.Context, jobs <-chan Job, numWorkers int) <-chan Result {
     results := make(chan Result, numWorkers)

     var wg sync.WaitGroup
     for range numWorkers {
         wg.Add(1)
         go func() {
             defer wg.Done()
             for job := range jobs {
                 select {
                 case <-ctx.Done():
                     return
                 default:
                     results <- process(job)
                 }
             }
         }()
     }

     go func() {
         wg.Wait()
         close(results)
     }()

     return results
 }
```

---

## 6. Testing

### Table-Driven Tests

```go
 func TestCalculate(t *testing.T) {
     tests := []struct {
         name     string
         a, b     int
         op       string
         expected int
         wantErr  bool
     }{
         {"add", 2, 3, "+", 5, false},
         {"subtract", 5, 3, "-", 2, false},
         {"divide by zero", 5, 0, "/", 0, true},
     }

     for _, tt := range tests {
         t.Run(tt.name, func(t *testing.T) {
             got, err := Calculate(tt.a, tt.b, tt.op)
             if tt.wantErr {
                 if err == nil {
                     t.Errorf("expected error, got nil")
                 }
                 return
             }
             if err != nil {
                 t.Errorf("unexpected error: %v", err)
                 return
             }
             if got != tt.expected {
                 t.Errorf("Calculate(%d, %d, %q) = %d, want %d",
                     tt.a, tt.b, tt.op, got, tt.expected)
             }
         })
     }
 }
```

### testify — Assertions & Mocks

```go
 import "github.com/stretchr/testify/assert"
 import "github.com/stretchr/testify/require"
 import "github.com/stretchr/testify/mock"

 func TestSomething(t *testing.T) {
     // Assert continues on failure
     assert.Equal(t, 42, result)
     assert.NoError(t, err)

     // Require stops test on failure
     require.NotNil(t, obj)
     require.NoError(t, err)

     // Now safe to use obj
     assert.Equal(t, "expected", obj.Name)
 }

// Mock with testify/mock
 type MockRepository struct {
     mock.Mock
 }

 func (m *MockRepository) GetUser(id string) (*User, error) {
     args := m.Called(id)
     if args.Get(0) == nil {
         return nil, args.Error(1)
     }
     return args.Get(0).(*User), args.Error(1)
 }

 func TestService(t *testing.T) {
     repo := new(MockRepository)
     repo.On("GetUser", "123").Return(&User{ID: "123", Name: "Alice"}, nil)

     svc := NewService(repo)
     user, err := svc.GetUser("123")

     require.NoError(t, err)
     assert.Equal(t, "Alice", user.Name)
     repo.AssertExpectations(t)
 }
```

### Fuzzing (Go 1.18+)

```go
 func FuzzParse(f *testing.F) {
     // Seed corpus
     f.Add("hello")
     f.Add("12345")

     f.Fuzz(func(t *testing.T, input string) {
         result, err := Parse(input)
         if err != nil {
             t.Skip()  // invalid input is ok
         }
         // Invariant: result must be valid
         if result.Len() < 0 {
             t.Errorf("negative length: %d", result.Len())
         }
     })
 }
```

```bash
go test -fuzz=FuzzParse -fuzztime=30s ./...
```

### Benchmarks

```go
 func BenchmarkProcess(b *testing.B) {
     data := generateLargeDataset()
     b.ResetTimer()
     for range b.N {
         Process(data)
     }
 }

// Memory allocation benchmark
 func BenchmarkAlloc(b *testing.B) {
     b.ReportAllocs()
     for range b.N {
         _ = make([]byte, 1024)
     }
 }
```

```bash
go test -bench=. -benchmem ./...
```

### Integration Tests

```go
//go:build integration

 func TestDatabase(t *testing.T) {
     if testing.Short() {
         t.Skip("skipping integration test")
     }

     db := setupTestDB(t)
     defer teardownTestDB(t, db)

     // Run tests against real database
 }
```

```bash
go test -tags=integration ./...
go test -short ./...          # skip integration
```

---

## 7. HTTP Servers

### Standard Library (`net/http`)

```go
 package main

 import (
     "encoding/json"
     "log"
     "net/http"
     "time"
 )

 type Handler struct {
     service Service
 }

 func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
     id := r.PathValue("id")  // Go 1.22+ path values

     user, err := h.service.GetUser(r.Context(), id)
     if err != nil {
         if errors.Is(err, ErrNotFound) {
             http.Error(w, "Not found", http.StatusNotFound)
             return
         }
         log.Printf("error: %v", err)
         http.Error(w, "Internal error", http.StatusInternalServerError)
         return
     }

     w.Header().Set("Content-Type", "application/json")
     json.NewEncoder(w).Encode(user)
 }

 func main() {
     mux := http.NewServeMux()
     h := &Handler{service: NewService()}

     // Go 1.22+ routing
     mux.HandleFunc("GET /users/{id}", h.GetUser)
     mux.HandleFunc("POST /users", h.CreateUser)
     mux.HandleFunc("GET /users", h.ListUsers)

     server := &http.Server{
         Addr:         ":8080",
         Handler:      mux,
         ReadTimeout:  5 * time.Second,
         WriteTimeout: 10 * time.Second,
         IdleTimeout:  120 * time.Second,
     }

     log.Println("Server starting on :8080")
     if err := server.ListenAndServe(); err != nil {
         log.Fatal(err)
     }
 }
```

### Chi Router

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

 r := chi.NewRouter()
 r.Use(middleware.Logger)
 r.Use(middleware.Recoverer)
 r.Use(middleware.RequestID)
 r.Use(middleware.Timeout(30 * time.Second))

 r.Route("/api/v1", func(r chi.Router) {
     r.Use(authMiddleware)

     r.Get("/users", listUsers)
     r.Post("/users", createUser)
     r.Get("/users/{id}", getUser)
     r.Put("/users/{id}", updateUser)
     r.Delete("/users/{id}", deleteUser)
 })

 http.ListenAndServe(":8080", r)
```

### Gin

```go
 import "github.com/gin-gonic/gin"

 r := gin.Default()

 r.GET("/users/:id", func(c *gin.Context) {
     id := c.Param("id")
     user, err := service.GetUser(c.Request.Context(), id)
     if err != nil {
         if errors.Is(err, ErrNotFound) {
             c.JSON(404, gin.H{"error": "not found"})
             return
         }
         c.JSON(500, gin.H{"error": "internal error"})
         return
     }
     c.JSON(200, user)
 })

 r.POST("/users", func(c *gin.Context) {
     var req CreateUserRequest
     if err := c.ShouldBindJSON(&req); err != nil {
         c.JSON(400, gin.H{"error": err.Error()})
         return
     }
     user, err := service.CreateUser(c.Request.Context(), req)
     if err != nil {
         c.JSON(500, gin.H{"error": err.Error()})
         return
     }
     c.JSON(201, user)
 })

 r.Run(":8080")
```

### Middleware Patterns

```go
// Logging middleware
 func LoggingMiddleware(next http.Handler) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
         start := time.Now()
         next.ServeHTTP(w, r)
         log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
     })
 }

// Authentication middleware
 func AuthMiddleware(next

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [kinhluan](https://github.com/kinhluan)
- **Source:** [kinhluan/skills](https://github.com/kinhluan/skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** yes
- **Shell / process execution:** yes
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-kinhluan-skills-golang-development
- Seller: https://agentstack.voostack.com/s/kinhluan
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
