go-template/README.md

222 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.24 | Modules, toolchain directive |
| Logging | [zap](https://github.com/uber-go/zap) | Structured JSON logging + `WithField` extension |
| Config | [viper](https://github.com/spf13/viper) | YAML file + env var overlay |
| 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 |
| Mocks | [mockery v2](https://vektra.github.io/mockery/latest/) | Type-safe, EXPECT()-style generated mocks |
| Tests | [testify](https://github.com/stretchr/testify) | Assertions (`assert`/`require`) + mock framework |
| Git hooks | custom pre-commit | gofmt + golangci-lint + gosec on every commit |
| 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.24+
- 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 <this-repo> 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 config/dev.yaml
```
---
## Daily workflow
```bash
make test # run all tests
make test-race # … with race detector
make lint # golangci-lint
make lint-fix # auto-fix what can be fixed
make security # gosec + govulncheck
make mocks # regenerate mocks after interface changes
make generate # run all //go:generate directives
```
> **Keyboard shortcut (VSCode):** `Ctrl+Shift+B` → build, `Ctrl+Shift+T` → test.
---
## Project structure
```
.
├── cmd/
│ └── app/
│ └── main.go # Composition root (thin — just wiring)
├── config/
│ └── dev.yaml # Local dev config (safe to commit)
├── internal/
│ ├── config/ # Viper config loader
│ ├── logger/ # Zap wrapper with WithField
│ └── greeter/ # Example domain package (replace with yours)
├── mocks/
│ └── greeter/ # Generated by `make mocks` — commit these
├── .devcontainer/ # VSCode / Codespaces container definition
├── .githooks/ # pre-commit hook (installed by `make setup`)
├── .vscode/ # launch, tasks, editor settings
├── tools.go # Tool version pinning (build tag: tools)
├── .golangci.yml # Linter configuration
├── .mockery.yaml # Mockery configuration
└── Makefile # All development commands
```
---
## Configuration
Config is loaded from a YAML file (default `config/dev.yaml`) with env var overrides.
```yaml
app:
name: "go-template"
port: 8080
env: "dev" # dev | staging | prod
logger:
level: "debug" # debug | info | warn | error
```
Override any key at runtime:
```bash
APP_PORT=9090 LOGGER_LEVEL=info make run
```
The `CONFIG_PATH` env var points to the config file:
```bash
CONFIG_PATH=config/staging.yaml ./bin/app
```
---
## Logging
The logger wrapper adds `WithField` and `WithFields` for ergonomic context chaining:
```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. In development, use
`logger.NewDevelopment()` for coloured console output.
---
## Mocking
1. Define your interface in an `internal/` package.
2. Add a `//go:generate mockery --name=YourInterface` directive.
3. Register the interface in `.mockery.yaml` under `packages:`.
4. Run `make mocks`.
Generated mocks support type-safe EXPECT() call chains:
```go
import mocks "gitea.djmil.dev/djmil/go-template/mocks/greeter"
func TestSomething(t *testing.T) {
g := mocks.NewMockGreeter(t)
g.EXPECT().Greet("Alice").Return("Hello, Alice!", nil)
result, err := myConsumer(g)
require.NoError(t, err)
}
```
---
## Git hooks
The pre-commit hook runs **gofmt + golangci-lint + gosec** before every commit.
It checks only staged Go files to keep it fast.
`govulncheck` is intentionally excluded from the hook (it makes network calls
and can be slow). Run it manually with `make security`.
To skip the hook in an emergency:
```bash
git commit --no-verify -m "wip: skip hooks"
```
---
## 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/vektra/mockery/v2@latest
go mod tidy
make tools # reinstall binaries
```
---
## Devcontainer
Open this repo in VSCode and choose **"Reopen in Container"** — everything
(Go toolchain, golangci-lint, mockery, gosec, govulncheck) is 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`