A production Go API foundation you own outright: net/http and chi, pgx with sqlc, an OpenAPI contract, OpenTelemetry, and a generator that keeps the codebase growing.
A complete Go service with the production concerns already wired in, and a generator that adds new resources in the same shape, so the scaffold keeps working on day 200, not just day 1. Clone it, rename the module, and there is nothing to import at runtime and nothing to lock into.
HTTP
- Standard
net/httphandlers routed by chi, with no framework context. api/openapi.yamlis the source of truth. Request validation and the typed server interface are generated from it; CI fails if they drift.- RFC 9457
application/problem+jsonerrors, each carrying the active trace ID. - Readiness-first graceful drain:
/readyzflips to 503, then in-flight requests bleed off, then the pool closes.
Data
- pgx/v5 with a tuned pool and a transaction helper.
- sqlc generates type-safe queries from plain SQL checked against the real schema.
- goose migrations, embedded into the binary and run as an explicit deploy step.
- A transactional outbox writes events in the same transaction as the state change.
- Idempotency keys make unsafe requests safe to retry.
Auth
- Bearer tokens verified against JWKS (OIDC) or a configured RSA public key.
- Role-based access checks in the service layer, with a single seam for OPA or Casbin.
- In development an ephemeral key is generated and a usable token is logged at startup.
Observability
- OpenTelemetry traces over OTLP and Prometheus metrics at
/metrics(unauthenticated by convention; restrict it at the network layer in production). - slog with
trace_idandspan_idon every line, and a handler that redacts secrets like passwords and tokens before they are logged. /livezand/readyzprobes, plus pprof and expvar on a separate guarded port.
Testing
- Integration tests run against a real Postgres via testcontainers, isolated per test by cloning a migrated template database so they are safe to run in parallel.
- Unit tests cover business logic with no database.
Supply chain
- GitHub Actions pinned to commit SHAs, kept current by Dependabot.
govulncheckand CodeQL gate the build; an OpenAPI and sqlc drift gate keeps generated code honest.- A multi-stage build produces a distroless, non-root, static image.
- GoReleaser publishes signed binaries (cosign keyless) with an SBOM.
git clone https://github.com/y0f/go-api-scaffolding
cd go-api-scaffolding
task up # builds and starts Postgres, runs migrations, starts the APIThe API listens on :8080. In development it prints a bearer token at startup;
copy it from the logs:
export TOKEN="<token from the api startup log>"
# List widgets (public)
curl -s localhost:8080/v1/widgets
# Create one (requires the token)
curl -s -X POST localhost:8080/v1/widgets \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name":"first"}'Prefer running the binary directly? Install the toolchain and point it at any Postgres:
task setup
task migrate
task runSee .env.example for every setting.
The generator stamps a new vertical slice (SQL, store, service, handler, test, and
a migration) in the same layout as the example widget module:
go run ./cmd/forge add resource OrderIt prints the next steps: register the queries file with sqlc, run task generate,
mount the handler (passing the auth verifier), grant the resource's write permission,
and apply the migration with task migrate. Generated modules are secure by default:
writes are authenticated and authorized and errors render as problem+json, but they
are self-contained chi handlers rather than spec-first. The internal/modules/widget
slice shows the full spec-first pattern with the generated OpenAPI server interface
and idempotency.
task generate # regenerate sqlc and OpenAPI code
task lint # golangci-lint v2
task test # unit tests with the race detector and coverage
task test:integration # integration tests against real Postgres (needs Docker)
task vuln # govulncheck
task observe # full stack with OpenTelemetry Collector, Tempo, Prometheus, Grafana
task build # build api, migrate, and forge into ./binWith task observe running, Grafana is on http://localhost:3000 with Prometheus
and Tempo already provisioned, and traces flow from the API through the Collector
into Tempo.
cmd/api service entrypoint and composition root
cmd/migrate migration runner
cmd/forge resource generator
internal/ config, server, auth, observability, platform, modules
api/ the OpenAPI contract
migrations/ versioned SQL, embedded into the binaries
deployments/ Dockerfile, docker compose, observability configs
docs/ architecture notes and ADRs
The architecture, request flow, and the reasoning behind each choice are in
docs/architecture.md and docs/adr.
An AGENTS.md describes the build and conventions for coding agents.
Because api/openapi.yaml is the single source of truth, the operations convert
cleanly into tool or function schemas for an LLM without hand-written glue.
The module path is github.com/y0f/go-api-scaffolding. To make it yours:
grep -rl github.com/y0f/go-api-scaffolding . \
| xargs sed -i 's#github.com/y0f/go-api-scaffolding#github.com/you/yourapp#g'
go mod edit -module github.com/you/yourapp
go mod tidyThe FORGE_ environment prefix and the forge generator name are independent of
the module path; rename them too if you like.
Released binaries are signed with cosign keyless signing. Verify the checksums:
cosign verify-blob \
--bundle checksums.txt.bundle \
--certificate-identity-regexp 'https://github.com/y0f/go-api-scaffolding' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums.txtMIT. See LICENSE.
