go-template/README.md
djmil 3403a8e168 result package
- usage tests as examples
- test utils
2026-04-01 18:43:28 +00:00

217 lines
6.1 KiB
Markdown

# go-template
A batteries-included Go project template optimised for **PoC and hobby projects**.
Clone it, rename the module, run `make init`, and you're coding.
---
## Features
| Area | Tool | Purpose |
|---|---|---|
| Language | Go 1.25 | Modules, toolchain directive |
| Logging | standard `log/slog` | Structured JSON/text logging + `WithField` extension |
| 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.go` + `go.mod` | Single source of truth for tool versions |
| 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/djmil/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, installs tools, configures git hooks
```
### 3. Build and run
```bash
make build # compiles to ./bin/app
make run # go run with default flags
```
---
## 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
```
> **Keyboard shortcut (VSCode):** `Ctrl+Shift+B` → build, `Ctrl+Shift+T` → test.
---
## Project structure
```
.
├── cmd/
│ └── app/
│ └── main.go # Composition root (thin — just wiring)
├── internal/
│ ├── config/ # flag-based config (config.Load)
│ ├── logger/ # slog wrapper with WithField / WithFields
│ └── greeter/ # Example domain package (replace with yours)
├── .devcontainer/ # VSCode / Codespaces container definition
├── .githooks/ # pre-push hook (installed by `make setup`)
├── .vscode/ # launch, tasks, editor settings
├── tools.go # Tool version pinning (build tag: tools)
├── .golangci.yml # Linter configuration
└── Makefile # All development commands
```
---
## Configuration
All configuration is via CLI flags with sensible defaults:
```bash
./bin/app -help
-env string environment: dev | staging | prod (default "dev")
-log-level string log level: debug | info | warn | error (default "info")
-name string application name (default "go-template")
-port int listen port (default 8080)
```
Override at runtime:
```bash
./bin/app -port 9090 -env prod -log-level warn
```
---
## Logging
The logger wrapper adds `WithField` and `WithFields` for ergonomic context chaining
on top of the standard `log/slog` package:
```go
log.WithField("request_id", rid).
WithField("user", uid).
Info("handling request")
log.WithFields(map[string]any{
"component": "greeter",
"name": name,
}).Debug("generating greeting")
```
In production (`app.env != dev`) the output is JSON (`slog.NewJSONHandler`).
In development, `logger.NewDevelopment()` uses the human-friendly text handler.
---
## Testing
Tests use the standard `testing` package. Concrete types are returned from
constructors — consumers (including tests) define their own minimal interfaces
and satisfy them with manual fakes. No code generation required.
```go
// Interface declared in the consumer (or _test.go), not in greeter package.
type greeter interface {
Greet(name string) result.Expect[string]
}
type fakeGreeter struct {
greetFn func(name string) result.Expect[string]
}
func (f *fakeGreeter) Greet(name string) result.Expect[string] {
return f.greetFn(name)
}
func TestSomething(t *testing.T) {
fake := &fakeGreeter{
greetFn: func(name string) result.Expect[string] {
return result.Ok("Hello, " + name + "!")
},
}
// pass fake to the system under test
}
```
---
## Git hooks
The pre-push hook runs **gofmt + go vet + golangci-lint + gosec** against the
full codebase before every `git push`. Running on push (not commit) keeps the
inner commit loop fast while still blocking bad code from reaching the remote.
`govulncheck` is intentionally excluded (it makes network calls and can be
slow). Run it manually with `make security`.
To skip the hook in an emergency:
```bash
git push --no-verify
```
---
## Tool version management
Tool versions are pinned via `tools.go` (a `//go:build tools` file) and tracked
in `go.mod`/`go.sum`. This ensures every developer and CI run uses exactly the
same versions.
To update a tool:
```bash
go get github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go mod tidy
make tools # reinstall binaries
```
---
## Devcontainer
Open this repo in VSCode and choose **"Reopen in Container"** — the Go
toolchain and all tools (golangci-lint, gosec, govulncheck) are installed
automatically via `make init`.
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 `.github/workflows/ci.yml` running `make lint test security`
- **Docker** — add a multi-stage `Dockerfile` for the binary
- **OpenTelemetry** — add tracing with `go.opentelemetry.io/otel`