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:
pkg.go.dev/go.uber.org/fx
uber-go.github.io/fx
github.com/uber-go/fx
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 single
Run()
. 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, but
init()
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.Annotate
for tags rather than wrapping a constructor in an
fx.Out
struct — keeps the constructor reusable outside fx.
Replace
fx.Provide
with
fx.Supply
for 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-dig
skill for the underlying container,
dig.In
/
dig.Out
, and DI without lifecycle
→ See
samber/cc-skills-golang@golang-dependency-injection
skill for DI concepts and library comparison
→ See
samber/cc-skills-golang@golang-samber-do
skill for a generics-based alternative without reflection
→ See
samber/cc-skills-golang@golang-google-wire
skill for compile-time DI (no runtime container)
→ See
samber/cc-skills-golang@golang-structs-interfaces
skill for interface design patterns
→ See
samber/cc-skills-golang@golang-context
skill for context propagation in OnStart/OnStop hooks
→ See
samber/cc-skills-golang@golang-testing
skill for general testing patterns
If you encounter a bug or unexpected behavior in uber-go/fx, open an issue at
https://github.com/uber-go/fx/issues
.
golang-uber-fx
安装
npx skills add https://github.com/samber/cc-skills-golang --skill golang-uber-fx