- store build metadata (version, commit, date) - multi-binary build: `make build` builds all packages from ./cmd - devcontainer adds ./bin to the PATH by default
5.9 KiB
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
- Go 1.25+
- Git
- (Optional) VSCode + Go extension
- (Optional) Docker for devcontainer
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.