Wire Go application graphs with uber-go/dig's reflection-based container.
Coding
golang-uber-fx
Try itWires long-running Go services with uber-go/fx: DI container, lifecycle hooks, modules, signal-aware Run.
What it does
Patterns and decision rules for assembling Go applications with uber-go/fx. Covers fx.New / fx.Provide / fx.Invoke, fx.Lifecycle OnStart/OnStop hooks with timeout-bounded context, fx.Module composition with scoped decorators, fx.Annotate for name/group/As bindings, value groups (group:"routes"), fx.Supply / fx.Replace / fx.Decorate, and fx.WithLogger. Includes a fx-vs-dig comparison, the boot lifecycle (New validates → Start fires OnStart → Run blocks on SIGINT/SIGTERM → Stop fires OnStop in reverse), default 15-second timeouts via fx.StartTimeout / fx.StopTimeout, and testing via fxtest.
When to use it
- Wire an HTTP or gRPC server with graceful shutdown
- Compose a service from named fx.Module units (db, http, metrics)
- Lifecycle-manage background workers that drain on SIGTERM
- Validate the dependency graph in CI via fx.New(...).Err()
The skill document
Persona: You are a Go architect building a long-running service with fx. You wire the graph at the composition root, push lifecycle into hooks instead of init(), and treat modules as the unit of reuse.
Using uber-go/fx for Application Wiring in Go
Application framework combining a reflection-based DI container (built on uber-go/dig) with a lifecycle, module system, signal-aware run loop, and structured event logging. For long-running services where boot order, graceful shutdown, and modular composition matter.
Official Resources:
This skill is not exhaustive. Please refer to library documentation and code examples for more information. Context7 can help as a discoverability platform.
go get go.uber.org/fx
fx vs. dig
fx is built on top of dig and shares the same reflection-based container engine. The DI primitives (Provide, Invoke, In/Out structs, named values, value groups) are identical — fx.In/fx.Out are re-exports of dig.In/dig.Out.
What fx adds on top:
| Concern | dig | fx |
|---|---|---|
| DI container | ✅ dig.New() | ✅ (embedded) |
| Lifecycle hooks | ❌ | ✅ fx.Lifecycle OnStart/OnStop |
| Module system | ❌ | ✅ fx.Module with scoped decorators |
| Signal-aware run loop | ❌ | ✅ app.Run() blocks on SIGINT/SIGTERM |
| Structured event logging | ❌ | ✅ fx.WithLogger / fxevent |
| Startup/shutdown timeout | ❌ | ✅ fx.StartTimeout / fx.StopTimeout |
Choose fx for long-running services (HTTP servers, workers, daemons) — lifecycle and signal handling are mandatory there, and modules make large service graphs manageable.
Choose raw dig when you need wiring without a framework: CLI tools, libraries that expose a container to callers, test harnesses, or embedding DI into an existing app that manages its own lifecycle. See samber/cc-skills-golang@golang-uber-dig skill.
The Application
import "go.uber.org/fx"
app := fx.New(
fx.Provide(NewLogger, NewDatabase, NewServer),
fx.Invoke(RegisterRoutes),
)
app.Run() // blocks until SIGINT/SIGTERM, then runs OnStop hooks
Boot stages: fx.New validates types (constructors do not run); app.Start(ctx) runs each fx.Invoke and fires OnStart hooks in topological order; main blocks on app.Done(); app.Stop(ctx) fires OnStop hooks in reverse order. Default timeout is 15 seconds — override with fx.StartTimeout / fx.StopTimeout.
Provide and Invoke
fx.New(
fx.Provide(NewLogger, NewDatabase, NewServer), // lazy
fx.Invoke(RegisterRoutes, StartMetricsExporter), // always run during Start
)
fx.Provide registers constructors; fx.Invoke is the trigger — without an Invoke (directly or transitively) referencing a type, its constructor never runs.
Lifecycle Hooks
Inject fx.Lifecycle and append hooks. Constructors should return quickly; long-running work belongs in OnStart.
func NewHTTPServer(lc fx.Lifecycle, log *zap.Logger, cfg *Config) *http.Server {
srv := &http.Server{Addr: cfg.Addr}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
ln, err := net.Listen("tcp", srv.Addr)
if err != nil { return err }
go srv.Serve(ln) // blocking work in a goroutine
return nil
},
OnStop: func(ctx context.Context) error {
return srv.Shutdown(ctx)
},
})
return srv
}
Both callbacks receive a context bounded by StartTimeout/StopTimeout — respect cancellation. OnStart must return quickly — spawn a goroutine for blocking work; otherwise startup hangs and dependent hooks never fire.
fx.StartHook / fx.StopHook / fx.StartStopHook adapt simpler signatures (no context, no error, or both):
lc.Append(fx.StartStopHook(srv.Start, srv.Stop)) // matched pair
Parameter and Result Objects
fx re-exports dig's dig.In / dig.Out as fx.In / fx.Out. Use them when a constructor has 4+ dependencies, or when you need name/group/optional tags.
type ServerParams struct {
fx.In
Logger *zap.Logger
DB *sql.DB
Cache *redis.Client `optional:"true"`
Routes []http.Handler `group:"routes"`
}
func NewServer(p ServerParams) *Server { /* ... */ }
fx.Annotate
fx.Annotate wraps a constructor to add tags or interface bindings without a fx.Out struct. Prefer it for ergonomic name/group/As bindings:
fx.Provide(
fx.Annotate(NewPrimaryDB, fx.ResultTags(`name:"primary"`)),
fx.Annotate(NewPostgresDB, fx.As(new(Database))), // expose interface
fx.Annotate(NewUserHandler,
fx.As(new(http.Handler)),
fx.ResultTags(`group:"routes"`),
),
)
Value Groups
Many constructors, one consumer slice — typical for routes, health checks, metrics collectors:
type RouteResult struct {
fx.Out
Handler http.Handler `group:"routes"`
}
type ServerParams struct {
fx.In
Routes []http.Handler `group:"routes"`
}
Append ,flatten (group:"routes,flatten") to unwrap a slice instead of nesting it. Order is not guaranteed — provide an explicit ordered slice when sequence matters.
fx.Module
fx.Module groups providers, invokes, and decorators under a name. Modules scope decorators to themselves and their children — a logger renamed in fx.Module("db", ...) only appears renamed for code inside that module.
var DatabaseModule = fx.Module("database",
fx.Provide(NewConnection, NewUserRepository),
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("db")
}),
)
func main() {
fx.New(
fx.Provide(NewConfig, NewLogger),
DatabaseModule,
HTTPModule,
).Run()
}
Treat each module as a small library that can be lifted into another app — its public surface is the types it Provides.
For fx.Supply/fx.Replace/fx.Decorate, optional deps, custom logging, manual lifecycle, and Quick Reference, see advanced.md.
Best Practices
- Keep
main()thin — providers, modules, and a singleRun(). Push real work into modules so each can be tested in isolation. - Use lifecycle hooks instead of
init()or goroutines launched from constructors — Start/Stop ordering depends on graph topology, butinit()goroutines do not, which leads to races and leaks. - OnStart must return promptly — long work goes in a goroutine inside the hook. A blocking OnStart hangs the rest of the boot.
- Respect
ctx.Done()in hooks — a hook that ignores cancellation is reported as a timeout failure but its goroutine continues, leaking resources. - Group by module, not by layer — a module owns the providers, lifecycle, and decorators for one concern (HTTP, DB, metrics).
- Use
fx.Annotatefor tags rather than wrapping a constructor in anfx.Outstruct — keeps the constructor reusable outside fx. - Replace
fx.Providewithfx.Supplyfor pre-built values (config, command-line flags). Shorter, signals intent. - Validate the graph in CI by booting under
fx.New(...).Err()— catches missing providers and cycles before deploy.
Common Mistakes
| Mistake | Fix |
|---|---|
| Long-running work directly in OnStart | Spawn a goroutine inside OnStart; the hook itself must return quickly so dependent hooks can run. |
fx.Provide something that should be fx.Supply | Pre-built values (config, secrets) belong in fx.Supply — clearer and avoids a no-op constructor. |
| Module decorator leaking to siblings | Decorate inside fx.Module(...) — decorators flow only to descendants. A top-level fx.Decorate is global. |
| Group order assumed | Groups are unordered. If order matters, provide an ordered slice from one constructor. |
| Constructors with side effects | Side effects belong in OnStart — constructors should be cheap and pure-ish, since they may run concurrently and lazily. |
Forgotten fx.Invoke | Without an Invoke (or downstream consumer), constructors never run. Add at least one Invoke per app. |
Testing
Use go.uber.org/fx/fxtest to integrate fx with *testing.T (failures call t.Fatal, RequireStop registers as t.Cleanup). fx.Populate(&target) pulls values out of the graph; fx.Replace swaps real dependencies for fakes. Full patterns in testing.md.
Further Reading
- advanced.md — Supply/Replace/Decorate, optional deps, custom event logging, manual lifecycle, full Quick Reference
- recipes.md — full HTTP service with database/metrics, background workers with graceful drain, multiple impls of the same interface, manual lifecycle for CLI embedding
- testing.md — fxtest patterns,
fx.Replace,fx.Populate, isolated lifecycle tests, CI graph validation
Cross-References
- → See
samber/cc-skills-golang@golang-uber-digskill for the underlying container,dig.In/dig.Out, and DI without lifecycle - → See
samber/cc-skills-golang@golang-dependency-injectionskill for DI concepts and library comparison - → See
samber/cc-skills-golang@golang-samber-doskill for a generics-based alternative without reflection - → See
samber/cc-skills-golang@golang-google-wireskill for compile-time DI (no runtime container) - → See
samber/cc-skills-golang@golang-structs-interfacesskill for interface design patterns - → See
samber/cc-skills-golang@golang-contextskill for context propagation in OnStart/OnStop hooks - → See
samber/cc-skills-golang@golang-testingskill for general testing patterns
If you encounter a bug or unexpected behavior in uber-go/fx, open an issue at .
Related skills
Choose the right dependency injection approach for your Go project and get wiring code you can apply.
Compile-time dependency injection for Go using google/wire, with codegen catching missing dependencies before build.
Set up samber/do v2 dependency injection in Go with typed providers and graceful shutdown.
Build, extend, and review Go CLIs with Cobra + Viper — flags, config, exit codes, signals, completion, testing.
Recommends production-ready Golang libraries and frameworks. Apply when the user explicitly asks for library suggestions, wants to compare alternatives, needs to choose a library for a specific task, or when a new dependency is being added to the project.