Reusable Go API foundation for That Software Company. The generated application name is {{APP_NAME}}.
Requirements
Go 1.26.0 or newer. CI and Docker currently validate Go 1.26.7.
PostgreSQL 16 or newer for database-backed execution.
Docker and Docker Compose for the container workflow.
Local execution
PostgreSQL and authentication are enabled by default. DATABASE_URL, Ed25519 PEM files, JWT metadata, and a CSRF secret are required in that mode. Copy .env.example to a local .env outside version control, set the database values, generate development keys, set AUTH_CSRF_SECRET in the shell, and export the variables before starting:
./scripts/generate-dev-auth-keys.sh
set -a
source .env
set +a
export AUTH_CSRF_SECRET="$(openssl rand -hex 32)"
go run ./cmd/api
The key generator writes only to the ignored .local/auth/ directory and never overwrites existing files. Production keys must be mounted by a secret manager or protected volume; never commit them. The interactive administrator command requires PostgreSQL and prompts for the password without echoing it:
go run ./cmd/auth -command create-admin
To run the API without PostgreSQL:
DATABASE_ENABLED=false go run ./cmd/api
The application validates all environment variables at startup. APP_ENV must be development, test, or production.
Docker Compose
Set the local-only POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, and AUTH_CSRF_SECRET variables in your shell or a local .env file. Generate the development key files before running:
./scripts/generate-dev-auth-keys.sh
docker compose up --build
Compose starts PostgreSQL with a healthcheck, runs development migrations on API startup, and stores PostgreSQL data in a named local volume. The production image is built separately:
docker build --target production -t example-api:local .
The production container runs as a non-root user. Production migrations must be executed explicitly with the migrate binary or go run ./cmd/migrate; automatic startup migrations are rejected in production.
Migrations
go run ./cmd/migrate -command up
go run ./cmd/migrate -command down -steps 1
go run ./cmd/migrate -command version
The migration directory uses normal .up.sql and .down.sql files. The migrations create error_events and the authentication schema (users, roles, permissions, refresh-token families, and login attempts).
Operational endpoints
GET /__ping checks only that the process is alive. It never queries PostgreSQL.
GET /api/v1/health checks readiness. It reports database: "disabled", "up", or "down"; the latter returns HTTP 503 without exposing failure details.
GET /api/v1/auth/csrf issues a signed CSRF token.
POST /api/v1/auth/login, /refresh, and /logout use CSRF-protected cookies; tokens are never returned in JSON.
GET /api/v1/auth/me returns the authenticated user, roles, and permissions.
GET /api/v1/internal/errors?endpoint=<path> requires an access cookie and the explicit errors:read permission.
Every response includes a validated X-Correlation-ID. Error responses include the same value in the JSON error object.
Architecture
This is a modular monolith with a simple MVC flow:
routes -> controller -> service -> repository/client
internal/modules/health owns health transport and readiness rules.
internal/modules/errors owns safe error listing behind authentication and errors:read authorization.
internal/app/routes.go is the application-owned extension point for registering product modules.
app.Dependencies.Auth exposes the template authentication service so product routes can compose auth.RequireRole and auth.RequirePermission without duplicating token or cookie handling.
OpenAPI files live in docs/openapi/ and are separate from controllers.
The template owns the operational composition in cmd/api, including /__ping and /api/v1/health. A generated project must not add product routes to those files or to internal/modules/health. Add product modules under internal/modules/<business-module>/ and register them from internal/app/routes.go; the template updater preserves that extension point.
Product endpoints that require authorization should wrap their handlers with the template middleware and explicit permissions. Roles are an additional boundary, not an implicit permission grant:
Unauthenticated requests receive 401; authenticated requests without the required role or permission receive 403. Keep the authorization decision in the module composition and leave the template-managed auth module unchanged.
Security
The foundation emits structured JSON logs through log/slog, security headers, an explicit CORS allowlist, and correlation IDs. It never logs request bodies, authorization headers, cookies, passwords, tokens, or secrets. Persisted HTTP 5xx events contain only the safe fields documented by the error_events migration.
Authentication uses Argon2id for 15–128-character passwords, Ed25519/EdDSA JWT access tokens with a 15-minute lifetime, and opaque refresh tokens with a 30-day default lifetime. Refresh tokens are stored only as hashes, rotated, revoked by family, and invalidated on reuse. Session cookies are HttpOnly, have no Domain, use Secure=false/SameSite=Lax in development and test, and Secure=true/SameSite=Strict in production. Login, refresh, and logout require the signed double-submit CSRF token in X-CSRF-Token.
In production, frontend and backend should be served under the same public origin. CORS credentials are enabled only for explicitly allowed origins; * is never accepted.
When PostgreSQL is disabled, the API still starts and operational endpoints remain available; authentication endpoints return 503 service_unavailable because no user store exists.
Tests and quality checks
go mod tidy
go mod verify
test -z "$(gofmt -l .)"
go vet ./...
go test ./...
go test -race ./...
go build ./cmd/api
go build ./cmd/auth
go build ./cmd/migrate
go build ./cmd/template
# Template lifecycle and shell checks
bash -n scripts/*.sh
./scripts/test-template-lifecycle.sh
Integration tests require PostgreSQL and use the integration build tag:
TEST_DATABASE_URL='postgres://USER:PASSWORD@localhost:5432/DB?sslmode=disable' go test -tags=integration ./...
The CI workflow runs the integration suite against PostgreSQL 16 and also performs Docker build and smoke checks. The hardening release measures critical behavior and scenarios rather than requiring an arbitrary 100% line coverage threshold.
Setup script
The Bash setup script safely configures a generated project without arbitrary overwrites:
The setup script resolves the source commit for the published template_version tag and records it in template_commit; --template-commit can provide an explicit override. It never uses the generated repository's own commit as template provenance. It records generated_from from --generated-from, defaulting to the generated module path, and preserves both values on subsequent idempotent runs.
Validate the template manifest and required files with:
./scripts/validate-template.sh
Template metadata and updates
.template/manifest.json records the source repository, template version, template commit, generated origin, compatibility, dependencies, and update policy. The update automation detects new template versions, opens PRs in derived repositories, enforces compatibility, and leaves breaking-change records and application-specific conflicts for manual review.
The generated repository also includes a scheduled and manually dispatchable template-update workflow. It looks for vMAJOR.MINOR.PATCH tags, applies a normalized patch when possible and otherwise uses a three-way merge from the recorded template_commit, checks Go and PostgreSQL compatibility, detects pre-applied files, reports unresolved paths, records new provenance only after a complete update, and opens a pull request. It never merges automatically. The repository owner must allow GitHub Actions to create pull requests and review generated changes manually.
If an older generated project recorded its own repository commit instead of the template commit, the workflow resolves provenance from the matching release tag and opens a small repair pull request automatically.
Required derived-repository onboarding
Complete these steps immediately after generating a repository from this template and before running the template-update workflow:
Create a dedicated fine-grained personal access token or GitHub App installation token. Scope it to the generated repository only and grant:
Contents: Read and write
Workflows: Read and write
Pull requests: Read and write
Add it to the generated repository under Settings -> Secrets and variables -> Actions as the repository secret TEMPLATE_UPDATE_TOKEN.
In Settings -> Actions -> General, allow read and write workflow permissions and allow GitHub Actions to create pull requests when the organization policy exposes that option.
Run Template update through Actions -> Template update -> Run workflow once and verify that it can create its update branch and pull request.
GitHub's built-in GITHUB_TOKEN is retained as a fallback for updates that do not modify workflow files, but it is not sufficient for the full template lifecycle. Without TEMPLATE_UPDATE_TOKEN, a workflow update can fail with refusing to allow a GitHub App to create or update workflow ... without workflows permission. Never commit the token or place it in .env files. If the organization requires approval for fine-grained tokens, the token must be approved before it can write to the generated repository.
If an update reports a conflict in .github/workflows/template-update.yml, preserve the template's latest provenance and update logic together with the TEMPLATE_UPDATE_TOKEN checkout and GH_TOKEN configuration. Run the generated repository tests before committing the manually resolved update.
The template maintainer must publish version tags such as v0.1.0 before derived repositories can detect releases. The initial release tag should point to the merged template commit.
Release roadmap
The current release line is 0.4.2. v0.3.1 reconciles the protected application-route extension point that was merged after the original v0.3.0 tag. v0.4.0 adds supply-chain security and safer lifecycle automation. v0.4.1 synchronizes manifest dependency metadata, and v0.4.2 adds a legacy-update metadata bootstrap. The immutable v0.2.6, v0.2.7, and v0.2.8 tags remain historical; new generated repositories should use the latest release tag. The planned releases are:
0.3.1: release metadata reconciliation for the post-v0.3.0 protected application-route extension point.
0.4.1: manifest dependency metadata synchronization during derived-repository updates.
0.4.2: pre-update manifest bootstrap for repositories using older updater scripts.
0.5.0: provider-agnostic same-origin deployment contract and trusted reverse-proxy configuration.
1.0.0: final validation from a clean testing-templatev2 repository.
Public registration, password recovery, Google OAuth, frontend implementation, and cloud-provider-specific deployment are not part of the current backend template.
Supply-chain controls
Dependabot groups weekly minor and patch updates for Go modules and GitHub Actions. Major updates remain separate for manual review. Dependency Review blocks high and critical dependency findings in pull requests. CI runs the pinned govulncheck version and Docker Scout against the locally built production image; fixable high and critical image findings block the workflow. Docker Scout requires DOCKER_SCOUT_HUB_USER and DOCKER_SCOUT_HUB_PASSWORD, containing a read-only Docker Hub identity and PAT. Store those names both as Actions secrets for normal pull requests and as Dependabot secrets for Dependabot-triggered workflows; GitHub does not expose Actions secrets to Dependabot workflows.
All Actions are pinned to immutable commit SHAs. Keep the version comment when updating a pin so Dependabot can identify the intended release. scripts/validate-action-pins.sh rejects tags, branches, malformed SHAs, and pins without a human-readable version comment.
Workflow YAML is validated with actionlint v1.7.12 by scripts/validate-workflows.sh. The validator runs in CI and from scripts/validate-template.sh; keep shell heredocs and YAML block scalars correctly indented, or prefer printf when generating small reports inside workflow steps.
.github/security-exceptions.json is empty by default. A temporary exception must identify one scanner finding and component exactly, include a reason, owner, GitHub issue, and future expires_on date. Expired, incomplete, wildcard, or unmatched exceptions fail validation. Exceptions never allow an unpinned Action.
After review and merge, the backend and frontend repositories must be marked as GitHub Template Repositories from Settings -> General -> Template repository. This is a post-merge checklist item, not an automated repository mutation.