Developer experience
This repository treats developer tooling as a reproducible product surface. The default path uses Make, Node’s standard library, committed configuration, and the tools already required by CI. Biome is the only JavaScript/TypeScript linter; ESLint is intentionally absent.
Supported development hosts
| Host | Supported path | Required software |
|---|---|---|
| Windows 11 | Git Bash or WSL2; Docker Desktop using the WSL2 engine | Git, Go 1.27, Node 22, Corepack, GNU Make, Docker Compose v2 |
| macOS 14+ | Native terminal and Docker Desktop/Colima | Xcode CLI tools, Go 1.27, Node 22, Corepack, GNU Make, Docker Compose v2 |
| Ubuntu 24.04+ | Native shell and Docker Engine | build-essential, Git, Go 1.27, Node 22, Corepack, Docker Compose v2 |
The devcontainer is the portable fallback. Open the repository in VS Code and select Dev Containers: Reopen in Container.
Version managers
mise install reads the committed .mise.toml and installs Go, Node, and pnpm together. If only Node is managed, fnm install reads .node-version, then corepack enable activates the pinned pnpm version. Direct installers remain supported as long as versions match the table.
Windows PowerShell users can install prerequisites with winget, but should run Make and shell scripts from Git Bash:
winget install Git.Git GoLang.Go OpenJS.NodeJS.LTS Docker.DockerDesktop
corepack enable
macOS:
brew install go node pnpm make mkcert delve air
Ubuntu:
sudo apt update
sudo apt install -y build-essential git curl shellcheck
corepack enable
Daily commands
make hooks # Husky + lint-staged + commitlint
make dev-test # deterministic pre-push gates
./scripts/dev-all.sh # complete native mesh
make dev SVC=auth # one service with committed Air config
make open # shell, aggregate API docs, health
make open-docs # direct Scalar docs + standalone entry index
make test-watch # Go watch loop
make web-test-watch # Vitest watchers
make logs SVC=auth
make db-shell SVC=users
make psql
make redis-cli
Frontend staged files are formatted and linted by Biome. Go staged files use gofumpt. The pre-commit hook also runs gitleaks; install it before enabling hooks. The commit-msg hook applies Conventional Commits through commitlint.
Local services and test data
The lab overlay starts Mailpit at 127.0.0.1:8025. Optional database tools start with:
docker compose -p go-platform-lab -f infra/compose.base.yml -f infra/compose.lab.yml --profile tools up -d
RedisInsight is then at 127.0.0.1:5540, and pgweb at 127.0.0.1:8087. Both bind only to loopback.
make seed registers 20 deterministic users and distributes them across user, operator, auditor, and support roles. Their development-only password is printed by node scripts/devx.mjs fake-data. make seed-reset truncates only application schemas in the explicitly named go-platform-lab Compose project, reruns bootstrap seeds, and recreates the demo users. Never point these commands at another Compose project.
Testcontainers creates an isolated database/Redis pair per Go test fixture. Frontend tests reset MSW handlers after each test. Do not share mutable fixture rows between tests; create them inside the test and let the container or transaction own cleanup.
Generators
make new-service NAME=audit
make new-migration SVC=users NAME=add_locale
make new-handler SVC=users OP=listUsers
make db-diagram
node scripts/devx.mjs fake-data 50 csv
new-service copies the maintained services/_template and includes the OpenAPI document, generated interface baseline, reversible migration, Air configuration, deployment assets, and an e2e ping stub. It refuses unsafe names and existing destinations. new-migration allocates the next six-digit sequence and creates a timeout-bounded up/down pair. new-handler accepts only an operationId already present in that service’s OpenAPI document.
Environment documentation is generated from Go env/envDefault struct tags, committed examples, and Compose interpolation:
make check-env
pnpm docs:gen
Local HTTPS
Install mkcert, then run make https. The command creates ignored certificates under tmp/certs, enables secure cookies, and starts all four Vite entries on https://127.0.0.1:5173-5176. Each Vite server proxies API, health, docs, and WebSocket traffic to the HTTP gateway, preventing mixed-content failures.
Trust only the local mkcert CA. Never commit certificates or reuse them outside development.
Debugging and profiling
VS Code includes launch and attach configurations. For a terminal attach:
dlv attach <pid>
# or
make delve PID=<pid>
Start a single service through make dev SVC=<name> when breakpoints should survive rebuilds. Each service owns a committed .air.toml.
For CPU profiles, run the observability overlay and query the loopback-published pprof endpoint:
docker compose -p go-platform-lab -f infra/compose.base.yml -f infra/compose.observability.yml --profile obs up -d
go tool pprof http://127.0.0.1:6060/debug/pprof/profile?seconds=30
Local traces flow from services to the OpenTelemetry collector at 127.0.0.1:4318, then Tempo at 127.0.0.1:3200. Use the request ID and trace ID from structured logs to correlate a failed request. See observability for sampling and production differences.
API review and CI linting
CI runs shellcheck, actionlint, yamllint, markdownlint, Biome, revive’s exported comment rule, generated-header verification, linked TODO/FIXME scanning, and the error-wrapping convention check. OpenAPI PRs produce an oasdiff Markdown report; definite breaking changes fail the gate. Danger checks migration pairs, contract regeneration, PR size, and documentation impact from a trusted base-branch Dangerfile.
Generated files must start with their generator header. Never hand-edit them. Wrap returned errors with operation context and %w:
return fmt.Errorf("load profile %d: %w", id, err)
Use errors.Is or errors.As at decision boundaries. Do not stringify and recreate an error, because that destroys its causal chain.
Decisions
- Go watch (J14): not adopted. Go 1.27 still has no repository watch loop equivalent to Air; committed Air configurations are already portable and explicit.
- Task/Just (J23): not adopted. Make is already installed in CI, devcontainers, and documented host paths. A second task DSL would duplicate commands without removing a measured constraint.
- Internal npm mirror (J75): the committed
.npmrcprefers the local store and bounds retries. For a slow or isolated network, copy.npmrc.mirror.exampleoutside the repository, replace its URL, and setNPM_CONFIG_USERCONFIGto that file. Credentials stay outside Git. - Release notes (J74): Release Please owns versioned notes; CI verifies the release workflow and requires a non-empty Unreleased changelog section.
Delivery map
| Items | Evidence |
|---|---|
| J1-J4 | Make pre-push target, open target, Husky/lint-staged, local commitlint |
| J5-J8 | EditorConfig, VS Code settings/extensions/Delve, devcontainer |
| J9-J12 | shellcheck, actionlint, yamllint, markdownlint CI gates |
| J13-J15 | Per-service Air, documented watch decision, direct Scalar server |
| J16-J17 | Deterministic role-varied seed and lab-only reset |
| J18-J22 | Service/migration generators, DB/log shell helpers, direct docs |
| J23-J25 | Make decision, env validation, struct-tag documentation generator |
| J26-J32 | Changelog modal, mkcert HTTPS, Delve/watch, entries index, MSW Node server |
| J33-J36 | Fake-data CLI, DBML generator, oasdiff report and breaking gate |
| J37-J41 | PR/issue templates, CODEOWNERS, contributing and governance |
| J42-J47 | Support matrix, troubleshooting/FAQ/glossary, ADR template/index |
| J48-J50 | Grouped Renovate schedule, patch devDependency automerge, lockfile maintenance |
| J51-J55 | Mailpit, optional RedisInsight/pgweb, psql/redis-cli shortcuts |
| J56-J59 | Persistent env badge, troubleshooting link, handler/service generators |
| J60-J63 | Revive comments, linked debt scan, generated headers, wrapping lint |
| J64-J72 | Version managers, OS setup, pprof, Delve, tracing, test isolation |
| J73-J76 | Danger automation, release-note verification, mirror config, wrapping convention |