Four ways to get the infrastructure a backend service needs, each suited to a different situation. This page covers what nurago gives up along with what it provides. For the capability index, see /features/.
Table of contents
The Four Options
| Approach | What you adopt | What it costs |
|---|---|---|
| Standard library only | Nothing beyond Go | Every service re-derives retries, shutdown, redaction, and health checks |
| A framework | An application structure and its runtime | The structure reaches into your code and is expensive to leave |
| An internal shared library | Your own module | You maintain, test, document, and version it |
| nurago | Individual packages | An external dependency per package you import |
Against the Standard Library Alone
Go’s standard library covers a great deal of what a service does. net/http serves and calls, log/slog logs, database/sql handles pools and transactions, and context handles cancellation. Plenty of services need nothing more.
The gap shows up in the parts that are easy to write and hard to write correctly. Exponential backoff is a five-line loop that overflows into negative durations after enough attempts. Graceful shutdown is a signal handler that has to coordinate several components against a deadline. Log redaction is a regular expression until the first credential shaped slightly differently reaches the logs. Argon2id hashing is one function call plus a storage format that has to survive a parameter upgrade.
Forty of the seventy packages here are standard-library-only, so importing them adds no third-party code to your build. What they add over writing it yourself is the edge cases, with tests and documentation attached.
Use the standard library alone when the service is small enough that the edge cases will not arrive, or when adding any dependency is a policy decision.
Against a Framework
Go frameworks such as go-kit, Kratos, and Goa provide an application architecture: a service definition, a transport layer, middleware chains, and often code generation. They deliver more than a package collection can, because the parts were designed against one model and fit together by construction.
The trade is coupling. A framework’s abstractions appear in your handler signatures and your package layout, so leaving one means rewriting the code that touched it. nurago has no such structure to adopt. backoff.Schedule computes delays and knows nothing about your service; redact.Redactor takes a string and returns a string. Adopting one package is therefore a small decision, reversing it is a small decision, and either works inside a service already built on a framework.
That independence has a cost: the packages do not compose themselves, so the wiring decisions stay yours. bootstrap and the generated example service (/docs/service-scaffolding/) show one way to make them, and nothing forces you to follow it.
Choose a framework when a team wants one settled way to build every service and is willing to inherit its structure. Choose nurago when services differ from each other, or when the existing code cannot be restructured.
Against an Internal Shared Library
A team running several Go services tends to grow an internal common or platform module holding this material: retry helpers, a logger factory, a metrics wrapper, HTTP middleware. nurago covers the same ground, published and maintained in the open since 2016.
An internal library fits your conventions precisely, and that advantage does not go away. It also has to be maintained: tests, documentation, versioning, dependency updates, vulnerability response, and review time whenever someone changes a shared helper, most of it invisible until the person who did it leaves.
This module carries that cost instead: unit test coverage held at 100%, benchmarks with allocation gates that fail the build on a regression, generated documentation verified in CI, a vulnerability check on every run, and an OpenSSF Best Practices badge. The exported API is stable within v1, so upgrading is a version bump.
Keep the internal library for what encodes your domain and your organisation’s conventions; the generic infrastructure inside it is the part worth replacing.
Against Single-Purpose Packages
For many of the problems here, an alternative third-party package exists. Nothing about nurago argues against github.com/cenkalti/backoff or github.com/redis/go-redis, and in fact go-redis is what /packages/redis/ is built on.
Not every package has a counterpart to compare against. /packages/numtrie/, /packages/stringkey/, /packages/enumbitmap/, /packages/decint/, and /packages/phonekeypad/ are original, written because nothing off the shelf covered the problem. Where an alternative does exist, the package here is not automatically the weaker choice: several were written after the alternatives were measured and found wanting on correctness, allocation behaviour, or API.
What the collection adds on top of the individual implementations is consistency across the set. Every configurable package takes a New constructor with variadic WithXxx options. Every client exposes HealthCheck(ctx) error and registers in the health endpoint without an adapter. Every error state is an exported sentinel matchable with errors.Is, and every package carries a runnable example and a stated dependency footprint. The same coverage assembled from a dozen unrelated packages arrives with a dozen configuration idioms and a dozen error conventions.
There is a real counter-argument. One module means one release cadence, one maintainer, and one supply-chain relationship covering many concerns, and importing a single package still adds the whole module to your go.mod.
What nurago Does Not Provide
- No dependency injection container or service locator.
- No ORM or query builder.
database/sqland sqlx are what the SQL packages assume. - No gRPC, GraphQL, or message-broker abstraction layer. The Kafka, SQS, Redis, and Valkey clients are thin wrappers over the specific systems.
- No code generation from a service definition. The project generator copies and renames a working example service.
- No routing framework.
httpserveruses httprouter and stays out of the way. - No opinion about your application architecture.
Choosing
- Adding one capability to an existing Go service: import the package, ignore the rest. The dependency cost is on the package page.
- Starting a service from nothing:
make project(/docs/service-scaffolding/) gives a running service with packaging and tests. - Evaluating for a team: read /docs/dependency-footprint/ first, because it answers the standing objection to a 70-package module.
- Comparing against your internal library: pick the three packages you maintain most reluctantly and compare those.
The module is MIT licensed, so importing it places no copyleft obligation on your own code: retaining the copyright notice is the only condition, and commercial and closed-source use are covered.