6.1 KiB
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 | Structured JSON logging + WithField extension |
| Config | viper | YAML file + env var overlay |
| Linting | golangci-lint | Aggregated linters, one config file |
| Security | gosec + govulncheck | SAST + dependency vulnerability scan |
| Mocks | mockery v2 | Type-safe, EXPECT()-style generated mocks |
| Tests | 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 + Go extension
- (Optional) Docker for devcontainer
1. Clone and rename
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)
make init # fetches deps, installs tools, configures git hooks
3. Build and run
make build # compiles to ./bin/app
make run # go run with config/dev.yaml
Daily workflow
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.
app:
name: "go-template"
port: 8080
env: "dev" # dev | staging | prod
logger:
level: "debug" # debug | info | warn | error
Override any key at runtime:
APP_PORT=9090 LOGGER_LEVEL=info make run
The CONFIG_PATH env var points to the config file:
CONFIG_PATH=config/staging.yaml ./bin/app
Logging
The logger wrapper adds WithField and WithFields for ergonomic context chaining:
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
- Define your interface in an
internal/package. - Add a
//go:generate mockery --name=YourInterfacedirective. - Register the interface in
.mockery.yamlunderpackages:. - Run
make mocks.
Generated mocks support type-safe EXPECT() call chains:
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:
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:
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.