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
- APIs
- control planes
- concurrent network services
- background workers
Choose it when
- Prefer explicit service code and standard tooling
- Kubernetes or Go infrastructure integration matters
- PostgreSQL already owns business state
Consider another path when
- Need a batteries-included product framework
- Hard real-time or no-GC constraints
- Rich client UI is the primary product
Working practices
- Propagate contexts and set timeouts on external requests. Bound concurrency and handle shutdown.
- Keep SQL and migrations reviewed and generated query code synchronized with the schema.
- Test handlers with httptest and persistence against a disposable real PostgreSQL instance.
- Use River only when durable jobs are required; test retry-safe effects and worker shutdown.
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
- Recommendation: compose net/http handlers around small domain packages and explicit interfaces at external boundaries. Pass request contexts into SQL and outbound calls. Use bounded errgroup fan-out or a worker pool, with an overload policy separate from concurrency limits.
- Recommendation: maintain reviewed SQL migrations and sqlc-generated queries; share a pool, acquire transactions only around domain writes, and call generated WithTx for the transaction. A single global mutex around all queries recreates avoidable serialization.
- Recommendation: PostgreSQL-backed River workers can run with the web binary initially or in an independent process when their scaling or shutdown needs differ. Insert business state and a job in the same database transaction when atomic enqueue is required; cross-database work needs a separately designed outbox.
- Recommendation: retain an established identity provider, object store, email sender and broker during a service rewrite. Request/response, cookies, WebSocket application protocol, job payload and retry semantics are separate compatibility contracts.
Tools and feedback
- Recommendation: start from the existing repository; inspect go.mod, go.sum, schema migrations and queue configuration. For a new local source project, go mod init example.com/service creates a module; choose the actual module path and supported toolchain deliberately.
- Recommendation: pin sqlc using the maintained tool installer, review sqlc.yaml and SQL source, then run sqlc generate after query/schema changes. Regenerate in CI and compare output; generated typing does not validate production schema drift.
- Recommendation: run gofmt on changed files, go vet ./..., go test ./... and go test -race ./... on a supported target. Add HTTP contract fixtures and cancellation/queue crash tests against the selected database; these are proposed checks, not executed here.
- Recommendation: build the service with go build ./cmd/server when that entry point exists. Set server/header/body deadlines, worker limits, pool capacity and shutdown budget from the deployment shape. Profile a representative workload with Go pprof; performance and cost remain unmeasured.
Translations to avoid
- Avoid one fire-and-forget goroutine per email/payment: an acknowledged request can survive while its side effect disappears on process crash.
- Avoid spawning unlimited goroutines to hide slow SQL. The downstream pool becomes the queue; cancellation, memory and overload must be bounded explicitly.
- Avoid replacing Redis solely because Go has channels. Shared fan-out, sessions, queue persistence and locks have different semantics.
- Avoid treating Server.Shutdown as complete WebSocket shutdown; hijacked connections need separate notification and tracking.
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.
Setup
- Start in a new source checkout in a local development workspace; run go mod init example.com/project using the real module path.
- Resolve and record Go/tool versions and dependencies; go get github.com/jackc/pgx/v5; add River only if jobs are needed.
- Create cmd/server plus application packages, typed settings, explicit http.Server limits, health/readiness and shutdown handling.
- Install selected sqlc and goose releases; write migrations and sqlc configuration; run sqlc generate. Configure a disposable PostgreSQL database through environment/secrets.
- With GOOSE_DRIVER=postgres and GOOSE_DBSTRING targeting only the disposable database, run goose -dir migrations up. Add business transaction and River enqueue tests.
- 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
- Which API contracts, browser login or service-token requirements apply?
- Does realtime need one-way updates or bidirectional sockets?
- Which database pool, worker throughput, storage and deployment/restore budgets apply?
Sources
- Go net/http
- templ
- HTMX
- pgx PostgreSQL driver
- sqlc documentation
- goose migrations
- golang-migrate
- Go os configuration primitives
- Go flag
- Go structured logging
- Go testing
- Go data race detector
- golangci-lint quick start
- Go vulnerability management
- Go HTTP testing
- River transactional enqueueing
- River resumable jobs
- Temporal Go SDK
- GoReleaser quick start
- go-oidc repository
- go-mail documentation
- AWS Go S3 examples
- coder/websocket documentation