Most guides to writing an HTTP service in Go stop at the handler. It is the part that is easiest to teach, and the smallest part of what it takes to run the service.
Production is not the environment the code was written in. The process does not run once on a laptop under a developer’s eye: it is started, restarted, scaled to many copies, rescheduled onto another node and killed mid-request by an orchestrator that knows nothing about what it was doing. It runs against configuration nobody re-read, on dependencies that are up but slow, under load that arrives in bursts, from clients that retry. Its failures are rarely a clean crash and usually a degradation: a few instances answering slowly, a connection pool exhausted since the last deploy, a timeout budget larger than the caller’s patience. Nobody is attached to the process, so what you learn about it afterwards is only what it wrote down while it was still working.
That is why the work that keeps a service alive sits around the handler rather than inside it: configuration that fails at startup instead of being wrong for six hours before anyone notices, a shutdown that drains in-flight requests before the socket closes, a readiness probe that answers no until the database is actually reachable, a retry policy that does not turn a slow upstream into a dead one, and signals detailed enough to be worth reading at three in the morning.
This guide is organised around the service’s runtime rather than around a feature list. What happens before the first request arrives, what happens during one, and what happens after the last one. Twenty pages, in six parts, building one real service.
Who this is for
You can write Go and you know what a 404 is. Nothing else is assumed.
Nothing Go-specific about serving HTTP is taken as given. net/http and the
types it gives you, routing, middleware, the request context, the
goroutine-per-request model, connection pools, graceful shutdown, health
endpoints and the delivery machinery around them are explained as the service
needs them, in the order it needs them. Read front to back and you get one
service assembled from main outwards.
The pages also stand on their own. Each one opens with the problem before any code, so if you have shipped services before you can go straight to the decisions you have not had to make yet, or to the ones you made differently and want to argue with. The parts that most often go unconsidered are Part IV and Part VI.
The service
The running example is an inventory service, and it is the
examples/service
reference implementation from nurago, a
collection of Go packages for the parts of a backend service that are not the
business logic.
The examples/ path names a template rather than a demo: it ships the Makefile,
CI workflows, OpenAPI specs, Docker files, the configuration layout a service
needs on day one, and a small working feature, four endpoints over an item
table. make project renames it into your own project, and this guide’s copy is
called inventorysvc.
Every Go snippet in this guide is code from that tree, at the path named above it. Where the guide goes past what the example ships, and Part V does that four times, the section says so in its first line.
The toolkit is a means and not the subject. Every page starts from a problem the service has, and reaches for a package only once the problem is on the table. The standard library’s own behaviour is stated before anything is wrapped around it, so you can see the boundary and decide what to keep. Where a reasonable engineer would pick something else, that alternative is named.
Part I. Orientation
The mental model the rest of the guide assumes.
Part II. Foundations
The three things every request depends on and none of them touch directly.
- Configuration That Is Wrong Before It Is Late
- The Lifecycle: One Context, One Channel, One Wait Group
- Logs You Can Search, and Secrets You Cannot Read
Part III. Serving
From a bound socket to a response body.
- Servers, Binders, and the Routes You Get for Free
- The Middleware You Will Actually Need
- One Request, All the Way Down
- The Envelope Argument
Part IV. Dependencies
Everything the service cannot do by itself.
- Talking to Other Services Without Losing the Thread
- The Database Connection Is a Long-Lived Thing
- Ping, Status, Metrics: Three Different Questions
Part V. A Real Feature, End to End
The item resource the example ships, from an OpenAPI operation to a row and back, and what it deliberately leaves out.
- Designing the Feature: Contract, Schema, Seams
- The Write Path: Decode, Validate, Commit
- The Read Path: What the Client Can Ask For
- What Is Reachable From Where
Part VI. Delivery
Getting it into production and keeping it there.
- Tests That Would Have Caught It
- Building, Packaging, and the Probes That Watch It
- Growing the Service Without Growing the Wiring
Going deeper on a single package
The guide covers the options a service actually sets. Several of the packages it uses have their own article here, going further into the engineering than a guide page has room for:
- Before the First Request and After the Last: a Go HTTP Server’s Edges
- A Production slog.Handler on Top of zerolog
- Redacting Secrets from Go Logs on a Performance Budget
- Observing Outbound HTTP in Go Without Disturbing It
- Between Two Attempts: What an HTTP Retry Loop Must Decide
- Exponential Backoff with Jitter in Go, Without the Overflow Footgun
- From singleflight to a Production Cache: a Gap Analysis
- Filtering Untrusted Client Queries in Go: Threat Model First
- A Distributed Lock in Go and MySQL, and Every Way the Session Can Betray You
The package reference and the comparison against other libraries live on nurago.org.
Start at What a REST Service Is Made Of.