mcpField
G
go-service

go-service

github.com/brojonatDevelopment139 installsUpdated 2w ago
Repository

About go-service

Go Service is a development skill for building Go microservices with the standard library, SQL, and command-line tools. It covers dependency injection, HTTP handlers, structured logging, database code generation, and testing workflows.

Key features

  1. 01Build services with the standard library
  2. 02Compose HTTP middleware
  3. 03Pass dependencies explicitly
  4. 04Use structured logging with slog
  5. 05Generate type-safe code with sqlc
  6. 06Build composable command-line interfaces

Use cases

  1. 01Design a Go microservice structure
  2. 02Implement HTTP handlers and middleware
  3. 03Generate code after SQL schema changes
  4. 04Build a CLI for a service

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/brojonat/llmsrules --skill go-service

After installing, type/go-servicein Claude Code to confirm it loaded.

Details

From the project README

Go Service

Production-grade Go microservices using the standard library, sqlc for SQL, urfave/cli for commands, and slog for structured logging.

Core Principles

  • Do one thing well: Single responsibility per component
  • Simple over complex: Plain JSON, standard SQL, environment variables
  • Compose: Design for piping and standard interfaces
  • Explicit dependencies: No globals, no singletons, pass everything as parameters

Project Structure

.
├── cmd/
│   ├── server/          # Backend service entry point
│   └── client/          # CLI entry point
├── cli/                 # CLI implementation
├── client/              # Public client library
├── internal/            # Internal packages
│   └── db/             # Generated code (from sqlc)
├── migrations/          # SQL schema migrations
├── queries/             # SQL queries for sqlc
├── examples/            # Usage examples
├── testdata/            # Test fixtures
├── Makefile
├── .air.toml           # Air configuration
├── sqlc.yaml           # sqlc configuration
├── go.mod
├── README.md
└── CHANGELOG.md

Handler Functions Pattern

Write functions that return http.Handler (following Mat Ryer's approach):

func handleListWallets(store TransactionStore, logger *slog.Logger) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()

        wallets, err := store.ListWallets(ctx)
        if err != nil {
            logger.ErrorContext(ctx, "failed to list wallets", "error", err)
            http.Error(w, "internal error", http.StatusInternalServerError)
            return
        }

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

Middleware Composition

type adapter func(http.Handler) http.Handler

// adaptHandler applies adapters in reverse order so the first is outermost
func adaptHandler(h http.Handler, adapters ...adapter) http.Handler {
    for i := len(adapters) - 1; i >= 0; i-- {
        h = adapters[i](h)
    }
    return h
}

// Usage:
mux.Handle("GET /wallets", adaptHandler(
    handleListWallets(store, logger),
    withRequestID(),
    withLogging(logger),
    withMetrics(promRegistry),
    withJWTAuth(jwtSecret),
))

Dependency Injection

// Bad: hidden dependency on global
var db *sql.DB
func SaveTransaction(txn *Transaction) error {
    _, err := db.Exec("INSERT INTO transactions ...")
    return err
}

// Good: explicit dependencies
type TransactionStore interface {
    Save(ctx context.Context, txn *Transaction) error
}

type PostgresStore struct {
    db *sql.DB
}

func NewPostgresStore(db *sql.DB) *PostgresStore {
    return &PostgresStore{db: db}
}

// Dependencies are clear at construction time
store := NewPostgresStore(db)
poller := NewWalletPoller(solanaClient, store, natsConn)

Structured Logging with slog

func setupLogger(level slog.Level) *slog.Logger {
    opts := &slog.HandlerOptions{Level: level}
    return slog.New(slog.NewJSONHandler(os.Stderr, opts))
}

// Most logs at DEBUG level
logger.DebugContext(ctx, "polling wallet", "wallet", walletAddress, "last_slot", lastSlot)

// INFO for lifecycle events only
logger.InfoContext(ctx, "server started", "addr", addr, "version", version)

// ERROR for failures
logger.ErrorContext(ctx, "failed to store transaction", "error", err, "signature", sig)

Control verbosity via LOG_LEVEL env var. Default to WARN in production, DEBUG in development.

SQL with sqlc

Write SQL, get type-safe Go:

# sqlc.yaml
version: "2"
sql:
  - engine: "postgresql"
    queries: "queries/"
    schema: "migrations/"
    gen:
      go:
        package: "db"
        out: "internal/db"

Always regenerate after schema changes and commit the generated code.

Error Handling

// Always handle errors
txns, err := client.GetTransactions(ctx)
if err != nil {
    return fmt.Errorf("failed to get transactions: %w", err)
}

CLI with urfave/cli

Clean, composable command-line interfaces with automatic env var binding and built-in help.

Development Workflow

  1. Plan: Design the interface first
  2. Test first (TDD): Write the test, write minimal code, refactor
  3. Server + Client: Every server feature gets a client method and CLI subcommand
  4. Makefile: make test, make lint, make dev, make build-server
  5. Hot reload: Use Air for development
  6. Linting: golangci-lint with strict settings, fix all warnings

Unix Philosophy

  • Be quiet by default: Only output on errors or significant events
  • JSON on stdout: Machine-readable output, --format=table|json|csv for humans
  • Errors to stderr: Keep stdout clean for piping
  • Exit codes: 0 = success, non-zero = error

Comments

Markdown supported