Library / SDK
samber/do avatar
samber/do

samber/do: a type-safe Go DI toolkit built on generics

⚙️ A dependency injection toolkit based on Go 1.18+ Generics.

2,815 stars113 forksGoMIT

At a glance

What is it?
samber/do is a dependency injection container for Go 1.18+ that uses generics instead of reflection or code generation. It fits services that need lifecycle hooks and scoped visibility, and it is a poor fit if you want a framework to own your application startup.
Who is it for?
Adopt samber/do if you have a Go service with several long-lived components that need ordered startup, health checks and dependency-aware shutdown, and you want the container to stay out of the way. Do not adopt it if you expect the library to drive your application's control flow, or if your codebase is still on Go 1.17 or older, since the API depends on generics.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem samber/do solves for Go services

Wiring a Go service by hand gets awkward once the object graph has depth. You construct a database pool, pass it to a repository, pass the repository to a service, pass the service to an HTTP handler, and every constructor signature grows. The README positions samber/do as an implementation of the Dependency Injection pattern that "may replace the fantastic uber/dig package", with the difference that samber/do uses Go 1.18+ generics and therefore offers a type-safe API. That last point is the whole pitch. With reflection-based containers, a mistake in the graph surfaces at runtime as an error string. With generics, the compiler knows the type you asked for.

The intended audience is Go engineers building services, workers or CLIs who already separate construction from behaviour and want that separation enforced by a container rather than by convention. The repository ships three project templates (do-template-api, do-template-worker, do-template-cli), which tells you the author expects this to sit under a server, not inside a library. The topic list on the repository includes lifecycle, graceful-shutdown, healthcheck and dependency-graph, and the feature list backs that up. If your program is a single main function that opens one file, none of this earns its keep.

How registration, invocation and the scope tree fit together

The container is built from services registered against types. The feature list separates registration (by type, by name, or several services from a package at once) from invocation (eager, lazy, transient, tag-based, with circular dependency detection). Those are two distinct phases, and the file layout reflects it: service.go, service_lazy.go, service_eager.go, service_transient.go and invoke.go sit alongside scope.go and root_scope.go.

Scopes are the second axis. The README calls them "a.k.a module" trees and lists visibility control and dependency grouping as their purpose. A scope is a container nested inside another, so a service registered in a child scope is not visible to the parent. That is the mechanism behind the nested-scope and package-system examples. Aliasing is handled separately, in service_alias.go, and the README distinguishes implicit aliasing (provide a struct, invoke the interface) from explicit aliasing (provide a struct, bind the interface, invoke the interface). The distinction matters when a concrete type is registered but consumers should only see an interface.

Lifecycle is the third piece. di_lifecycle.go and the healthcheckable and shutdownable examples cover health checks, graceful unload, lifecycle hooks and dependency-aware parallel shutdown. Parallel shutdown ordered by the dependency graph is the part hand-rolled wiring usually gets wrong: naive shutdown closes things in reverse registration order, which is not the same as reverse dependency order. The container also exposes introspection. di_explain.go, the service-explanation example and the http/ directory point at an explain API plus a web UI and HTTP middleware for std, Gin, Fiber, Echo and Chi. dag.go suggests the dependency graph can be resolved and visualised, as the feature list claims.

Installing samber/do v2 and registering a first service

The README gives the install command for the current major version. Note the /v2 suffix: the module path in go.mod is github.com/samber/do/v2, so imports must carry it too.

bash
go get github.com/samber/do/v2@latest

The README also documents v1 as go get github.com/samber/[email protected] and points at a migration page for moving from v1 to v2. Pick one path and stay on it; mixing the two module paths in one repository will produce confusing type errors. The library has no dependencies except the Go standard library, and the README states there is no code generation, so nothing extra runs at build time.

What you write next is a provider function that the container calls when the service is first needed. The repository's examples directory is the reference for exact signatures (examples/simple, examples/struct, examples/interface), and the GoDoc for the v2 module is the authoritative API listing. The shape of the work is: create a container, register providers, then invoke the types you need. Registration is keyed by type, so the generic parameter on the invoke call is what selects the service. If you register a concrete struct and invoke an interface, the README calls that implicit aliasing and it works without an extra binding step.

For a first real use, follow the eager-loading and lazy-loading examples rather than inventing a layout. Lazy is the default mental model: nothing is constructed until something asks for it. Eager loading is for services that must exist at startup, such as a connection pool you want to fail fast on. Run the test target from the Makefile if you are working inside a fork of the repository itself:

bash
make test

The Makefile defines that target as go test -race ./..., so it runs the suite with the race detector enabled. The README also documents make tools to install dev dependencies and make watch-test for a reflex-driven loop.

Where samber/do stops being the right tool

The container resolves types, not intent. If two services need the same type but different configurations, the type system will not catch the mix-up; you need named registration or separate scopes, and the README lists register by name as a feature precisely because type-keyed registration is not always enough. This is the cost of the generics approach: the API is type-safe about the type, and silent about everything else.

Scope visibility is a second sharp edge. A service registered in a child scope is invisible to the parent, which is the point, but it also means a misplaced registration produces a lookup failure at the moment of invocation rather than at startup. The README lists circular dependency detection as a feature, which suggests cycles are a realistic failure mode in larger graphs. The library also does not generate code, so there is no build step to catch graph errors early; the graph is validated when the container runs.

Finally, samber/do is not an application framework. It does not own your HTTP server, your signal handling or your main loop. The README describes a toolkit and a container, and the templates exist because you are expected to assemble the surrounding structure yourself. If what you want is a framework that dictates the shape of main, this is the wrong dependency. The README also does not document rollback behaviour for partially constructed graphs, so plan for what happens when the third of five providers fails.

samber/do against uber/dig and uber/fx

The README names uber/dig directly as the package samber/do may replace, and the difference it states is the type-safe generics API. dig is reflection-based, so provider functions are registered and invoked through interface{} values and errors appear at runtime. samber/do puts the type in the function signature, so a mismatch is a compile error. That is a real difference in day-to-day feedback, not a marketing line.

The comparison with uber/fx is less explicit but the search data shows people compare them. fx is built on dig and adds an application lifecycle: it owns startup, shutdown and the run loop. samber/do covers some of the same ground through its lifecycle hooks, health checks and dependency-aware parallel shutdown, but the README presents those as container features, not as a framework. The practical consequence is that with fx you adopt a way of structuring main, and with samber/do you adopt a container you call from main. If your team already runs fx, the migration cost is not just the API; it is the removal of the framework layer.

The other alternatives worth naming are the ones the README lists as siblings from the same author: samber/lo for slice and map helpers, samber/mo for monads, and samber/ro for reactive programming. Those are not DI containers, and the search results that surface them alongside samber/do are name collisions rather than genuine substitutes.

Maintenance, licence and the upgrade bill

The repository is not archived, and the last push was on 2026-09-10. The most recent release listed is v2.1.0 on 2026-07-20, following v2.0.0 on 2025-09-21 and v2.0.0-rc1 on 2025-08-22. So the v2 line is roughly a year old and has had at least one minor release since. The README states the library follows SemVer strictly and that no breaking changes will be made to exported APIs before v3.0.0. That is a commitment in the README, not a guarantee, but it is a specific one and it is the thing to hold the project to.

The upgrade cost is concentrated at the v1 to v2 boundary, which is why the README links a dedicated migration page. Within v2, the SemVer promise means minor upgrades should not require code changes. The module path carries the major version, so v1 and v2 can coexist in a dependency graph without conflict, which softens the migration for large repositories.

Licensing is MIT, copyright Samuel Berthe, with the licence file at the repository root. MIT is permissive and imposes no source-disclosure obligation on your own code. This is not legal advice; if your organisation has a policy on third-party licences, route the LICENSE file through it. The dependency surface is small, which keeps the licence review short: go.mod lists go-type-to-string, testify, goleak and an indirect yaml dependency, with a comment stating that dependencies are excluded from releases and that CI is the place to check them.

Editorial conclusion

Adopt samber/do if you have a Go service with several long-lived components that need ordered startup, health checks and dependency-aware shutdown, and you want the container to stay out of the way. Do not adopt it if you expect the library to drive your application's control flow, or if your codebase is still on Go 1.17 or older, since the API depends on generics. Before committing, read the v1 to v2 migration page at do.samber.dev/docs/upgrading/from-v1-x-to-v2, confirm which module path your code will import, and check the examples directory for a scope layout close to yours.

Frequently asked questions

How does dependency injection in samber/do actually work internally?

You register providers against types, and the container constructs a service when something invokes that type. The README describes lazy, eager and transient loading modes, plus circular dependency detection, and the repository splits the implementation across service_lazy.go, service_eager.go and service_transient.go.

What are the three types of dependency injection in samber/do?

The README does not frame the library around the classic three-way split. What it documents is three loading modes: eager loading, lazy loading and transient loading, which control when and how often a service is constructed.

What is a real life example of dependency injection with samber/do?

The repository ships runnable examples for exactly this, including examples/simple, examples/struct, examples/interface, examples/healthcheckable, examples/shutdownable and examples/web-application. The web-application and event-driven examples are the closest thing to a production-shaped service.

What is dependency injection and how does it work in samber/do?

The README describes samber/do as an implementation of the Dependency Injection design pattern, where services are registered in a container and invoked by type. It uses Go 1.18+ generics rather than reflection, so the API is type-safe.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. samber/do on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/samber-do.svg)](https://hysenlabs.com/projects/samber-do)