Go to file Use this template
djmil 13f6a6812a result.Wrap - propagate a failed Expect into a new type U
- public API streamline
- Failf[T]("msg") -	originate a failure from a message; embed a cause with %w
- Err[T](err) - sets .err verbatim (the return zero, err / sentinel case)
2026-06-14 11:43:23 +00:00
.claude actualize claude instructions 2026-05-26 19:33:33 +00:00
.devcontainer build stamping 2026-06-13 20:32:24 +00:00
.githooks init: go/template v0.1.1 2026-04-07 20:32:27 +00:00
.vscode build stamping 2026-06-13 20:32:24 +00:00
cmd/app build stamping 2026-06-13 20:32:24 +00:00
docs slides 2026-05-04 19:15:11 +00:00
internal result.Wrap - propagate a failed Expect into a new type U 2026-06-14 11:43:23 +00:00
pkg result.Wrap - propagate a failed Expect into a new type U 2026-06-14 11:43:23 +00:00
.editorconfig init: go/template v0.1.1 2026-04-07 20:32:27 +00:00
.gitignore init: go/template v0.1.1 2026-04-07 20:32:27 +00:00
.golangci.yml init: go/template v0.1.1 2026-04-07 20:32:27 +00:00
CLAUDE.md result.Wrap - propagate a failed Expect into a new type U 2026-06-14 11:43:23 +00:00
go.mod init: go/template v0.1.1 2026-04-07 20:32:27 +00:00
go.sum init: go/template v0.1.1 2026-04-07 20:32:27 +00:00
LICENSE init: go/template v0.1.1 2026-04-07 20:32:27 +00:00
Makefile build stamping 2026-06-13 20:32:24 +00:00
README.md build stamping 2026-06-13 20:32:24 +00:00
rename.sh actualize claude instructions 2026-05-26 19:33:33 +00:00
tools.versions add go doc as tools 2026-04-08 18:28:06 +00:00

template

A Go project template for PoC, hobby projects, and small publishable packages. Clone it, rename the module, run make init, and you're coding.

Demonstrates idiomatic Go patterns: stdlib-only dependencies, consumer-defined interfaces, manual fakes for testing, and a clean go.mod that stays free of dev tool noise — so packages extracted from this template can be published without module graph pollution.


Design forces

Two libraries in this template emerged from a recurring friction in Go: mechanism leaking into intent.

pkg/result exists because if err != nil { return nil, err } repeated on every line obscures what the code is actually doing. The happy path drowns in error-routing boilerplate. result.Expect moves the exit logic to the edges — constructors and entry points — so the body of a function reads as intent, not plumbing.

pkg/logger exists because fmt.Fprintln(os.Stderr, "error: "+msg) leaks how (write to stderr, format a string) into code that should only express what (something went wrong). The same friction appeared in format selection: choosing between human text and JSON based on the execution environment is mechanism, not business logic. logger.NewCLI hides that choice — the call site just says log.Warn(...) and gets the right output for the context.

The pattern: constructors own the mechanism; call sites own the intent.


Features

Area Tool Purpose
Language Go 1.25 Modules, toolchain directive
Logging standard log/slog Auto-detects terminal vs pipe: human text or JSON; optional debug file dump
Config standard flag CLI flags with defaults, no config files
Linting golangci-lint Aggregated linters, one config file
Security gosec + govulncheck SAST + dependency vulnerability scan
Tests standard testing Table-driven tests, manual fakes, no third-party test framework
Git hooks custom pre-push gofmt + go vet + golangci-lint + gosec on every push
Tool versions tools.versions + go run Pinned versions without polluting go.mod
Dev environment devcontainer Reproducible VSCode / GitHub Codespaces setup
IDE VSCode Launch configs, tasks, recommended settings

Getting started

Prerequisites

1. Clone and rename

git clone https://gitea.djmil.dev/go/template my-project
cd my-project

# Interactive rename — updates module path, config, devcontainer, and docs:
./rename.sh

2. Init (run once)

make init          # fetches deps, configures git hooks
make tools         # (optional) install tool binaries to GOPATH/bin for IDE integration

3. Build and run

make build   # compiles every cmd/* binary to ./bin/, stamped with git commit + build time

Every subdirectory under cmd/ becomes a named binary in ./bin/. In the devcontainer, ./bin/ is on PATH automatically, so after make build you can run app (or any other binary) directly — with any flags — from any directory in the terminal.


Daily workflow

make test          # run all tests
make test-race     # … with race detector
make lint          # go vet + golangci-lint
make lint-fix      # go fix + golangci-lint auto-fix
make security      # gosec + govulncheck
make release       # list tags
make release VERSION=v0.1.0  # run full checks then tag+push

Keyboard shortcut (VSCode): Ctrl+Shift+B → build, Ctrl+Shift+T → test.

Claude Code commands

If you use Claude Code, the repo ships a custom slash command:

Command What it does
/new-package <name> Scaffolds internal/<name>/ with a concrete type, constructor, doc comments, and a black-box test file — all wired to project conventions

Commands live in .claude/commands/ and are available to anyone who clones the repo.


Project structure

.
├── cmd/
│   └── app/
│       ├── config.go        # Flag parsing and Config struct (all flags live here)
│       └── main.go          # Composition root (thin — just wiring)
├── internal/
│   └── greeter/             # Example domain package (replace with yours)
├── pkg/                     # Publishable packages — no app-specific assumptions
│   ├── logger/              # slog wrapper: NewCLI, New, NewWriter, WithField/WithFields
│   ├── result/              # Happy-path error handling (Expect[T])
│   └── testutil/            # Test helpers (ResultOk, Equal, …)
├── .devcontainer/           # VSCode / Codespaces container definition
├── .githooks/               # pre-push hook (gofmt + vet + lint + gosec)
├── .vscode/                 # Launch configs, tasks, editor settings
├── tools.versions           # Pinned tool versions (Makefile + hook source this)
├── .golangci.yml            # Linter configuration
└── Makefile                 # All development commands

Devcontainer

Open this repo in VSCode and choose "Reopen in Container". Run make init to configure git hooks, then make tools for IDE-integrated tool binaries.

Works with GitHub Codespaces out of the box.


Next steps (not included)

  • HTTP server — add chi or gin
  • Database — add sqlx or ent
  • CI — add a Gitea Actions / CI pipeline running make lint test security
  • Docker — add a multi-stage Dockerfile to publish the binary as a container image
  • OpenTelemetry — add tracing with go.opentelemetry.io/otel