# 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](https://golangci-lint.run) | Aggregated linters, one config file | | Security | [gosec](https://github.com/securego/gosec) + [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/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](https://code.visualstudio.com/) + [Go extension](https://marketplace.visualstudio.com/items?itemName=golang.Go) - (Optional) Docker for devcontainer ### 1. Clone and rename ```bash 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) ```bash make init # fetches deps, configures git hooks make tools # (optional) install tool binaries to GOPATH/bin for IDE integration ``` ### 3. Build and run ```bash 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 ```bash 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](https://claude.ai/code), the repo ships a custom slash command: | Command | What it does | |---|---| | `/new-package ` | Scaffolds `internal//` 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](https://github.com/go-chi/chi) or [gin](https://github.com/gin-gonic/gin) - **Database** — add [sqlx](https://github.com/jmoiron/sqlx) or [ent](https://entgo.io/) - **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`