agentsclimarketplace

Golang

Skill endorphin-ai/claude-code-teams/go-react-team/.claude/skills/golang

Golang backend development skill for REST APIs with Chi router, PostgreSQL, and layered architecture. Use when implementing Go backend code.From its SKILL.md

Install
npx -y skills add endorphin-ai/claude-code-teams --skill golang

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

13.3 KB, ~3.2k tokens by cl100k_base, as published. Nobody here has run it

Golang Backend Development

Purpose

This skill provides the knowledge and conventions for building production-grade Go REST APIs. It covers project structure, layered architecture (Handler -> Service -> Repository), database access with PostgreSQL, routing with Chi, and all supporting concerns (config, logging, error handling, validation, graceful shutdown). The go-dev agent uses this skill to implement backend API code that integrates with a React frontend.

Project Structure

project-root/
  cmd/
    server/
      main.go                # Entry point: config load, DI wiring, server start, graceful shutdown
  internal/
    config/
      config.go              # Struct with env tags, Load() function
    handler/
      health.go              # Health check handler
      {resource}.go          # HTTP handlers per resource (one file per domain entity)
      response.go            # Shared JSON response helpers (Success, Error, Paginated)
    service/
      {resource}.go          # Business logic per resource
    repository/
      {resource}.go          # Database queries per resource
    model/
      {resource}.go          # Domain structs, DB models
      errors.go              # Custom error types
    middleware/
      auth.go                # JWT/session auth middleware
      cors.go                # CORS configuration
      logging.go             # Request logging middleware
      recovery.go            # Panic recovery middleware
      request_id.go          # Request ID injection
    router/
      router.go              # Chi router setup, route registration, middleware chain
  migrations/
    000001_initial.up.sql    # golang-migrate format: {sequence}_{name}.{direction}.sql
    000001_initial.down.sql
  sqlc/                      # Optional: sqlc configuration and generated code
    sqlc.yaml
    query/
      {resource}.sql
    db/                      # Generated code (do not edit)
  go.mod
  go.sum
  Dockerfile
  .env.example

Key Patterns

Layered Architecture

The codebase follows strict layering. Dependencies flow inward only:

HTTP Request
  -> Middleware (auth, logging, CORS, recovery)
    -> Handler (parse request, validate, call service, write response)
      -> Service (business logic, orchestrate repos, enforce rules)
        -> Repository (database queries, no business logic)
          -> PostgreSQL

Rules:

  • Handlers NEVER import repository packages or access the database directly.
  • Repositories NEVER contain business logic, validation, or HTTP concepts.
  • Services own all business rules and orchestrate one or more repositories.
  • Each layer communicates through Go interfaces defined in the consuming package.

Dependency Injection via Constructor Functions

Every layer uses constructor injection. No global state, no init() for wiring.

// repository/user.go
type UserRepository struct {
    db *pgxpool.Pool
}

func NewUserRepository(db *pgxpool.Pool) *UserRepository {
    return &UserRepository{db: db}
}

// service/user.go
type UserService struct {
    repo *repository.UserRepository
}

func NewUserService(repo *repository.UserRepository) *UserService {
    return &UserService{repo: repo}
}

// handler/user.go
type UserHandler struct {
    svc *service.UserService
}

func NewUserHandler(svc *service.UserService) *UserHandler {
    return &UserHandler{svc: svc}
}

Wiring happens in cmd/server/main.go:

db := mustConnectDB(cfg)
userRepo := repository.NewUserRepository(db)
userSvc := service.NewUserService(userRepo)
userHandler := handler.NewUserHandler(userSvc)
r := router.New(userHandler, ...)

Chi Router

Chi is the preferred HTTP router. It is stdlib-compatible (net/http handlers).

package router

import (
    "github.com/go-chi/chi/v5"
    chimw "github.com/go-chi/chi/v5/middleware"
)

func New(userH *handler.UserHandler) chi.Router {
    r := chi.NewRouter()

    // Global middleware stack (order matters)
    r.Use(chimw.RequestID)
    r.Use(chimw.RealIP)
    r.Use(middleware.Logger)       // custom structured logging
    r.Use(middleware.Recovery)     // panic recovery
    r.Use(middleware.CORS)         // CORS for React frontend
    r.Use(chimw.Timeout(30 * time.Second))

    // Health check (no auth)
    r.Get("/healthz", handler.HealthCheck)

    // API routes
    r.Route("/api/v1", func(r chi.Router) {
        // Public routes
        r.Post("/auth/login", authH.Login)
        r.Post("/auth/register", authH.Register)

        // Protected routes
        r.Group(func(r chi.Router) {
            r.Use(middleware.Auth)

            r.Route("/users", func(r chi.Router) {
                r.Get("/", userH.List)
                r.Post("/", userH.Create)
                r.Route("/{id}", func(r chi.Router) {
                    r.Get("/", userH.GetByID)
                    r.Put("/", userH.Update)
                    r.Delete("/", userH.Delete)
                })
            })
        })
    })

    return r
}

PostgreSQL with pgx

Use pgxpool for connection pooling. Never use database/sql directly for PostgreSQL.

import "github.com/jackc/pgx/v5/pgxpool"

func ConnectDB(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) {
    config, err := pgxpool.ParseConfig(databaseURL)
    if err != nil {
        return nil, fmt.Errorf("parsing database URL: %w", err)
    }
    config.MaxConns = 25
    config.MinConns = 5
    config.MaxConnLifetime = time.Hour

    pool, err := pgxpool.NewWithConfig(ctx, config)
    if err != nil {
        return nil, fmt.Errorf("creating connection pool: %w", err)
    }
    if err := pool.Ping(ctx); err != nil {
        return nil, fmt.Errorf("pinging database: %w", err)
    }
    return pool, nil
}

Query option A: sqlc (preferred for type safety)

Define SQL in .sql files, generate Go code:

-- sqlc/query/user.sql
-- name: GetUserByID :one
SELECT id, email, name, created_at FROM users WHERE id = $1;

-- name: ListUsers :many
SELECT id, email, name, created_at FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2;

-- name: CreateUser :one
INSERT INTO users (email, name, password_hash) VALUES ($1, $2, $3) RETURNING id, email, name, created_at;

Query option B: GORM

Use only when project already has GORM or PRD specifies it. Prefer sqlc/pgx otherwise.

Database Migrations

Use golang-migrate with file-based migrations.

migrate create -ext sql -dir migrations -seq {name}
migrate -path migrations -database "$DATABASE_URL" up
migrate -path migrations -database "$DATABASE_URL" down 1

Migration files are pure SQL. One up file, one down file per migration. Down migrations must be reversible.

Error Handling

Define domain error types in internal/model/errors.go:

package model

import "errors"

var (
    ErrNotFound      = errors.New("resource not found")
    ErrConflict      = errors.New("resource already exists")
    ErrUnauthorized  = errors.New("unauthorized")
    ErrForbidden     = errors.New("forbidden")
    ErrBadRequest    = errors.New("bad request")
    ErrInternal      = errors.New("internal server error")
)

// ValidationError carries field-level validation details.
type ValidationError struct {
    Field   string `json:"field"`
    Message string `json:"message"`
}

type ValidationErrors []ValidationError

func (ve ValidationErrors) Error() string {
    return fmt.Sprintf("%d validation error(s)", len(ve))
}

Always wrap errors with context using fmt.Errorf("doing thing: %w", err). Handlers map domain errors to HTTP status codes:

func mapErrorToStatus(err error) int {
    switch {
    case errors.Is(err, model.ErrNotFound):
        return http.StatusNotFound
    case errors.Is(err, model.ErrConflict):
        return http.StatusConflict
    case errors.Is(err, model.ErrUnauthorized):
        return http.StatusUnauthorized
    case errors.Is(err, model.ErrForbidden):
        return http.StatusForbidden
    case errors.Is(err, model.ErrBadRequest):
        return http.StatusBadRequest
    default:
        return http.StatusInternalServerError
    }
}

Configuration

Use environment variables. Load with envconfig or viper.

package config

import "github.com/kelseyhightower/envconfig"

type Config struct {
    Port        int    `envconfig:"PORT" default:"8080"`
    DatabaseURL string `envconfig:"DATABASE_URL" required:"true"`
    JWTSecret   string `envconfig:"JWT_SECRET" required:"true"`
    CORSOrigins string `envconfig:"CORS_ORIGINS" default:"http://localhost:3000"`
    LogLevel    string `envconfig:"LOG_LEVEL" default:"info"`
    Environment string `envconfig:"ENVIRONMENT" default:"development"`
}

func Load() (*Config, error) {
    var cfg Config
    if err := envconfig.Process("", &cfg); err != nil {
        return nil, fmt.Errorf("loading config: %w", err)
    }
    return &cfg, nil
}

Request/Response JSON

Use dedicated request and response structs. Never bind directly to domain models.

// handler/user.go
type CreateUserRequest struct {
    Email    string `json:"email" validate:"required,email"`
    Name     string `json:"name" validate:"required,min=2,max=100"`
    Password string `json:"password" validate:"required,min=8"`
}

type UserResponse struct {
    ID        string    `json:"id"`
    Email     string    `json:"email"`
    Name      string    `json:"name"`
    CreatedAt time.Time `json:"created_at"`
}

Validate with go-playground/validator:

import "github.com/go-playground/validator/v10"

var validate = validator.New()

func decodeAndValidate[T any](r *http.Request) (T, error) {
    var req T
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        return req, fmt.Errorf("%w: %s", model.ErrBadRequest, err.Error())
    }
    if err := validate.Struct(req); err != nil {
        return req, toValidationErrors(err)
    }
    return req, nil
}

Structured Logging

Use log/slog (stdlib, Go 1.21+). No external logging libraries needed.

logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelInfo,
}))
slog.SetDefault(logger)

slog.Info("server starting", "port", cfg.Port, "env", cfg.Environment)
slog.Error("query failed", "err", err, "user_id", userID)

Graceful Shutdown

func main() {
    // ... setup ...

    srv := &http.Server{
        Addr:         fmt.Sprintf(":%d", cfg.Port),
        Handler:      r,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
    }

    go func() {
        slog.Info("server listening", "addr", srv.Addr)
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            slog.Error("server error", "err", err)
            os.Exit(1)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    slog.Info("shutting down server")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        slog.Error("server shutdown error", "err", err)
    }
    pool.Close()
    slog.Info("server stopped")
}

Docker Multi-Stage Build

FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /server ./cmd/server

FROM alpine:3.19
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /server .
COPY migrations/ ./migrations/
EXPOSE 8080
CMD ["./server"]

Conventions

Naming

  • Package names: lowercase, single word, no underscores. handler not handlers.
  • Files: lowercase, snake_case. user_repository.go.
  • Interfaces: verb-noun or -er suffix. UserReader, Authenticator.
  • Constructors: NewXxx pattern.
  • Error variables: ErrXxx pattern.
  • Context: always first parameter, named ctx.
  • Unexported helpers: keep in same file as caller.

Module Conventions

  • Module path matches repo: github.com/{org}/{repo}.
  • Run go mod tidy after adding dependencies.
  • Pin major versions. Use go get package@latest for updates.

Linting

Run golangci-lint run ./... before reporting. Key linters:

  • errcheck: all errors must be handled.
  • govet: catches common mistakes.
  • staticcheck: advanced static analysis.
  • unused: no dead code.

Testing Conventions

  • Test files: {name}_test.go in same package.
  • Table-driven tests for multiple cases.
  • Use testify/assert or stdlib testing only.
  • Repository tests use a test database or pgxmock.
  • Handler tests use httptest.NewRecorder() and httptest.NewRequest().

Comments

  • All exported functions, types, and constants MUST have doc comments.
  • Comment format: // FunctionName does X. (starts with the name).
  • Package comment in one file per package (usually doc.go or the primary file).

Knowledge Strategy

  • Patterns to capture: Successful handler/service/repo patterns, middleware implementations, query patterns, error handling approaches discovered during implementation.
  • Examples to collect: Working endpoint implementations (full vertical slice), migration patterns, test patterns.
  • Update permission: Agents may freely add/update files in references/. Changes to SKILL.md or scripts/ require user approval.

What ships with it: 6 files

33.8 KB alongside SKILL.md

workflows/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.