<!-- skill: Go Services | version: 1.0.0 | description: Build Go net/http services with goroutines, modules and clean deploys | category: System & Productivity -->
## Instructions
- Modules always: `go mod init example.com/svc`, pin Go in `go.mod` (`go 1.22`), `go mod tidy` before commit; vendoring only for air-gapped builds.
- Project layout: `cmd/api/main.go` entry, `internal/http`, `internal/store`, `internal/config`; `pkg/` only for shareable libs — never import `internal` cross-module.
- net/http servers: stdlib `http.ServeMux` + method patterns (`GET /api/v1/users/{id}`), `http.Server{ReadTimeout, WriteTimeout, IdleTimeout}`, graceful `Shutdown(ctx)`.
- Handlers: thin — decode JSON (limit `MaxBytesReader` 1MB), validate, call service, encode JSON with explicit status; `http.Error` never leaks internals (log full, return generic).
- Goroutines/channels: `go func` + `sync.WaitGroup` / `errgroup`, buffered channels for queues, `context.Context` for cancel/deadline everywhere; never bare `go` without lifecycle.
- Error wrapping: `fmt.Errorf("store: get user %s: %w", id, err)`, sentinel `var ErrNotFound = errors.New(...)` + `errors.Is/As`; map to HTTP codes in one `writeError` helper.
- Config: env via `os.Getenv` + `strconv` or `caarlos0/env`, fail fast on missing `PORT/DATABASE_URL`; no flags+env mix in one binary without docs.
- vet/test: `go vet ./...`, `gofmt -l .`, `golangci-lint run` in CI; table-driven tests, `httptest.NewServer`, race detector `go test -race ./...`.
- Middleware chain: request-id, structured `slog` logging, recoverer, CORS, timeout (`http.TimeoutHandler`); order: recover → id → log → auth → route.
- DB: `database/sql` + `pgx` driver or `sqlc` generated queries; `context` timeouts per query, connection pool `SetMaxOpenConns(25)`, migrate with `golang-migrate`.
- JSON: `encoding/json` with `json:"fieldName"` tags, `DisallowUnknownFields` on strict inputs, `time.RFC3339` timestamps; stream big outputs with `json.Encoder`.
- Deploy: `goreleaser` for binaries (linux/arm darwin), systemd unit for VPS, or `ko`/multi-stage Dockerfile (`golang:1.22-alpine` → `scratch`/`distroless`).
## Examples
- REST users API: mux routes, in-memory + postgres store interface, wrapped errors to 404/409/500, slog middleware, `go test -race`, systemd deploy.
- Worker service: `errgroup` fan-out fetcher with semaphore(8), `context.WithTimeout` per request, retry with backoff, graceful shutdown on SIGTERM.
- Release pipeline: goreleaser `goreleaser release --snapshot --clean`, checksums + SBOM, systemd `svc.service` with `Restart=always`, `/healthz` probe.
## Workflows
1. Scaffold: `go mod init`, `cmd/api` + `internal/...` layout, `.golangci.yml` + Makefile.
2. Implement store interface first (mock + postgres), then HTTP handlers + middleware.
3. Wire config/env, logging, graceful shutdown, `/healthz` + `/readyz` endpoints.
4. Test: table tests, httptest routes, `go vet` + `go test -race ./...`.
5. Build: `CGO_ENABLED=0 go build -trimpath -ldflags="-s -w"` or goreleaser snapshot.
6. Deploy via systemd unit or distroless image behind reverse proxy with TLS.
## Templates
- HTTP server with graceful shutdown:
```go
// cmd/api/main.go
package main
import (
    "context" "log/slog" "net/http" "os"
    "os/signal" "syscall" "time"
)
func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte(`{"status":"ok"}`))
    })
    mux.HandleFunc("GET /api/v1/users/{id}", getUser)
    srv := &http.Server{
        Addr: ":" + envOr("PORT", "8080"), Handler: withMiddleware(mux),
        ReadTimeout: 10 * time.Second, WriteTimeout: 15 * time.Second,
        IdleTimeout: 60 * time.Second,
    }
    ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
    defer stop()
    go func() { slog.Info("listening", "addr", srv.Addr); _ = srv.ListenAndServe() }()
    <-ctx.Done()
    shut, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    _ = srv.Shutdown(shut)
}
func envOr(k, d string) string { if v := os.Getenv(k); v != "" { return v }; return d }
```
- Handler + wrapped errors:
```go
var ErrNotFound = errors.New("not found")
func getUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    u, err := store.Get(r.Context(), id)
    if errors.Is(err, ErrNotFound) {
        http.Error(w, `{"error":"user not found"}`, http.StatusNotFound); return
    }
    if err != nil {
        slog.Error("get user", "id", id, "err", err)
        http.Error(w, `{"error":"internal"}`, http.StatusInternalServerError); return
    }
    writeJSON(w, http.StatusOK, u)
}
func writeJSON(w http.ResponseWriter, code int, v any) {
    w.Header().Set("Content-Type", "application/json"); w.WriteHeader(code)
    _ = json.NewEncoder(w).Encode(v)
}
```
- systemd unit:
```ini
[Unit]
Description=Go API service
After=network.target
[Service]
ExecStart=/usr/local/bin/svc
Environment=PORT=8080 DATABASE_URL=postgres://svc:secret@localhost:5432/svc
Restart=always
RestartSec=3
User=svc
[Install]
WantedBy=multi-user.target
```
## Tools
- SARAH shell_run for `go build/vet/test -race`, `golangci-lint run`, `goreleaser release`, `systemctl status`.
- SARAH file_write for `cmd/` + `internal/` scaffolds, systemd units, Dockerfiles.
## Advanced
- Concurrency control: `golang.org/x/sync/errgroup` with `SetLimit(8)`, `singleflight` for stampede, `atomic` counters; pprof (`net/http/pprof` on localhost) for leaks.
- Observability: `log/slog` JSON handler, OpenTelemetry (`otelhttp` middleware), Prometheus `/metrics` via `promhttp`; trace-id in every log line.
- Hardening: `http.MaxBytesReader`, per-IP rate limit (`golang.org/x/time/rate`), timeouts on every client (`http.Client{Timeout}`), `gosec` scan in CI.
## Documentation
- https://go.dev/doc/
- https://pkg.go.dev/net/http
- https://pkg.go.dev/log/slog
- https://goreleaser.com/intro/
- https://pkg.go.dev/golang.org/x/sync/errgroup
