stack.menu

Go service

HTTP APIs and backend services with a straightforward binary deployment.

net/http · pgx · sqlc · PostgreSQL

Complete guide (Markdown) · Structured record (JSON)

Original source review: 2026-10-10. Core guidance rechecked: 2026-10-13. Check scope. Dependency combinations and setup need project-specific qualification.

Use cases

Choose it when

Consider another path when

Working practices

Tools by job

HTTP server: net/http

Standard handlers and request contexts minimize routing infrastructure.

Set request/body limits, deadlines and graceful shutdown; middleware and authorization remain application work.

Optional server-rendered UI: templ + HTMX

Compiled components and HTML-fragment updates suit forms and dashboards.

Generation adds a build step; advanced client state still needs browser-side design and testing.

Database and migrations: PostgreSQL + pgx + sqlc + goose

Visible SQL, generated typed access and one migration owner.

Generators do not prove query efficiency; review transaction boundaries, pool budgets and online migration behavior. Never mix migration owners.

Configuration: Typed settings loaded with os.LookupEnv

Explicit startup validation and standard structured logs.

Define precedence; reject malformed required values; redact secrets. Environment access alone is not validation or secret management.

Application logging: log/slog

Explicit startup validation and standard structured logs.

Define precedence; reject malformed required values; redact secrets. Environment access alone is not validation or secret management.

Tests and static checks: testing; go vet; race detector; govulncheck

Standard toolchain feedback plus exercised concurrency and known-vulnerability checks.

Race detector requires supported target/toolchain and exercised code; no scanner proves absence of vulnerabilities.

HTTP and database integration tests: httptest + real disposable PostgreSQL

Exercise protocols and actual SQL/migrations.

Database tests need an isolated database; mocks cannot establish PostgreSQL semantics.

Durable work: River for PostgreSQL-backed jobs

Transactional enqueueing ties work to committed business data.

External effects need idempotency. Default resumable checkpoints may be lost on crash; use transactional checkpoints where required. Inspect Pro feature boundaries.

Deployment and distribution: Go build binary in a supervised process or container; GoReleaser for releases

Small explicit runtime boundary; release automation builds target artifacts.

Target OS/architecture, libc/cgo, CA certificates, timezone data and signing need explicit verification. Snapshot builds are not publication.

Browser identity: go-oidc + existing OIDC provider

Provider discovery and ID-token verification support owned login sessions

Use v3 module; configure issuer/client, OAuth callback state and session lifecycle. go-oidc verifies ID tokens; API access-token validation follows the provider contract. Authorization stays near data.

Transactional email: go-mail + project-selected SMTP provider

Typed mail construction and SMTP transport support application messages

Configure TLS, sender credentials and timeouts; send required mail through durable jobs with duplicate protection. Validate chosen async/runtime features in Rust; delivery receipts and bounces need provider handling.

Uploads and object storage: AWS SDK for Go S3 client

Durable object storage avoids dependence on service-local disk

Configure runtime credentials, region and least-privilege bucket access; authorize object keys and uploads, bound size/concurrency and define retention. Test multipart cleanup and selected provider compatibility.

Realtime transport: coder/websocket

WebSocket support fits bidirectional connected clients

Enable the documented module/feature; authorize handshake and every protected message, bound queues/message size and implement heartbeat/shutdown. Socket delivery is not durable history; define reconnect/resync.

Build with the stack’s strengths

Architecture and developer workflow.

Recommendation: a Go HTTP process owns request admission and cancellation, domain packages own invariants, and a bounded SQL connection pool provides parallel database access. Durable jobs have their own persisted queue and worker lifecycle. Goroutines and buffered channels are concurrency mechanisms; graceful drain does not turn them into a recoverable queue.

Version scope: Go 1.27 release documentation and current net/http/context APIs; sqlc latest transaction guide with pgx/v5; River current documentation, PostgreSQL composition. Resolve and record Go toolchain, sqlc, pgx and River versions in go.mod/go.sum and tool configuration before implementation. Official release notes identify Go 1.27 as the August 2026 release; this research does not qualify a lockfile or patch-version/security support. · Reviewed 2026-10-11

Architecture boundaries

Tools and feedback

Translations to avoid

For an existing application

Keep the contracts while you change the stack.

These are documented options and proposed checks. Inspect the actual source application before adopting a transition.

Data and schema

Inspect the source

Inventory the old schema, isolation, identifiers, null handling, timestamps and transaction invariants before changing language or driver.

During coexistence

Recommendation: let the old application own migrations while Go accesses backward-compatible columns through sqlc; route writes for one domain/cohort to one owner. Expand schema before either runtime needs it.

Target design

Recommendation: sqlc plus pgx/v5 or database/sql, a shared bounded pool and explicit transaction-scoped queries. Keep the existing database if its guarantees fit; PostgreSQL replacement is a separate migration.

Rollback

Recommendation: return domain traffic to the old writer while preserving expanded columns. Contract migrations wait until the rollback window closes; reconcile any new-only writes first.

Benefits and prerequisites

Typed query access and concurrent pool usage can improve maintainability and remove an unnecessary application-wide lock.
Requires: Queries generated from the reviewed schema; representative null/time/transaction fixtures; a pool sized across all replicas.
Tradeoffs: Generated types do not prove isolation or business correctness; pool exhaustion and migrations still need operations.

Checks before transfer

  • Verify old-write/new-read and new-write/old-read round trips, including nulls and time zones.
  • Force a mid-transaction failure; neither half of the domain change may commit.
  • Saturate the pool and cancel a request; verify bounded waiting and connection release.
Jobs and schedules

Inspect the source

Capture existing queue kind, JSON arguments, retry/schedule/dead-letter states and external-effect idempotency. Redis/Resque or Sidekiq payloads are not River payloads.

During coexistence

Recommendation: keep old workers consuming old queues. Route selected job kinds into River through versioned producers; transform outstanding states explicitly and retain one scheduler. River Ruby integration is a possible bridge, not automatic Active Job execution.

Target design

Recommendation: River PostgreSQL jobs with domain writes plus InsertTx in the same transaction, explicit queue concurrency and bounded retry policy. Keep Redis for any remaining broker/session/cache responsibility.

Rollback

Recommendation: stop new River producers and reroute only unstarted work after reconciliation. Keep a compatible River consumer for already accepted jobs; a broker switch cannot undo sent messages or payments.

Benefits and prerequisites

Atomic enqueue closes the gap between committing domain state and creating work.
Requires: Domain rows and River insertion use the same transaction/database; validated job-kind/argument mapping; replay-safe external effects.
Tradeoffs: Adds queue schema/maintenance to the database; retries can repeat effects; backlog migration and adapter compatibility are separate work.

Checks before transfer

  • Abort the domain transaction; no job may become available.
  • Crash after sending an email but before job completion; retry must reconcile or reuse a stable external idempotency key.
  • Account for ready, scheduled, retry, failed and in-flight jobs before retiring the old consumer.
Delivery and recovery

Inspect the source

Preserve response/error behavior, overload rules, deadlines and restart guarantees; establish separate budgets for HTTP and background jobs.

During coexistence

Recommendation: serve a fixed tenant/endpoint cohort from Go behind existing routing, with errgroup/context cancellation inside each operation and deployment-level readiness. Keep the old instance ready for contract-compatible rollback.

Target design

Recommendation: signal.NotifyContext, bounded admission/fan-out, Server.Shutdown with a fresh timeout context, tracked worker termination and pool closure after accepted work ends.

Rollback

Recommendation: return routing to the old process and inspect committed outcomes and queued work before any replay. Preserve identical database/schema contracts for both versions.

Benefits and prerequisites

Cooperative cancellation limits work that callers no longer need and makes planned shutdown observable.
Requires: Every called operation honors context; in-flight tasks are tracked; the platform shutdown grace period exceeds the configured drain budget.
Tradeoffs: Cancellation does not reverse a committed write or external effect; forced termination still loses memory-only work.

Checks before transfer

  • Cancel a client while SQL/outbound I/O is blocked; verify resources and goroutines settle.
  • Send SIGTERM during a write; the server stops admitting traffic and records whether accepted work committed.
  • Force SIGKILL; verify durable jobs recover independently of graceful drain.
Realtime transport

Inspect the source

Preserve authentication, subscription identifiers, frame formats, ordering, reconnect cursors and cross-runtime fan-out. A WebSocket transport does not implement Action Cable automatically.

During coexistence

Recommendation: keep the existing broker and explicit application-protocol adapter while moving complete room/tenant cohorts, including reconnects, to Go. Publish/subscribe in both directions using captured protocol fixtures.

Target design

Recommendation: coder/websocket for transport plus bounded per-connection queues, tracked close handshakes and an application resynchronization path. Retain Redis Pub/Sub if ephemeral cross-process fan-out fits; add durable history separately where missed events matter.

Rollback

Recommendation: route the cohort and reconnects back together. Keep the broker and old protocol alive; rebuild client state from durable records rather than pretending ephemeral events can be replayed.

Benefits and prerequisites

Explicit connection ownership and queue limits bound slow-client resource use.
Requires: Capacity and disconnect policy are defined; clients can reconnect and reconstruct authoritative state; broker/protocol compatibility is tested.
Tradeoffs: Redis Pub/Sub is at-most-once and does not replay disconnected messages; replacing it with process-local channels removes cross-instance delivery.

Checks before transfer

  • Connect an old and new client to the same room; verify broadcasts in both directions and revocation.
  • Disconnect a subscriber and publish; reconnect must fetch missed authoritative state.
  • Throttle one client; verify the configured queue overflow policy and independent delivery to others.

Read the migration workflow →

Setup

  1. Start in a new source checkout in a local development workspace; run go mod init example.com/project using the real module path.
  2. Resolve and record Go/tool versions and dependencies; go get github.com/jackc/pgx/v5; add River only if jobs are needed.
  3. Create cmd/server plus application packages, typed settings, explicit http.Server limits, health/readiness and shutdown handling.
  4. Install selected sqlc and goose releases; write migrations and sqlc configuration; run sqlc generate. Configure a disposable PostgreSQL database through environment/secrets.
  5. With GOOSE_DRIVER=postgres and GOOSE_DBSTRING targeting only the disposable database, run goose -dir migrations up. Add business transaction and River enqueue tests.
  6. Add the chosen release configuration, pin tools and preserve go.mod/go.sum; document build and rollback. These steps have not been run.

Verification

Run these in the configured application; they are instructions, not recorded passes.

gofmt -l .
go test ./...
go test -race ./...
go vet ./...
govulncheck ./...
sqlc generate
goose -dir migrations status
go build ./cmd/server

Open questions

Sources