Labsco
samber logo

golang-uber-fx

2,400

by samber · part of samber/cc-skills-golang

Golang application framework using uber-go/fx — fx.New, fx.Provide, fx.Invoke, fx.Module, fx.Lifecycle hooks, fx.Annotate (name/group/As), fx.Decorate, fx.Supply, fx.Replace, fx.WithLogger, and signal-aware Run(). Apply when using or adopting uber-go/fx, when the codebase imports `go.uber.org/fx`, or when wiring services with fx.New. For raw DI without lifecycle, see `samber/cc-skills-golang@golang-uber-dig` skill.

🔥🔥🔥✓ VerifiedFreeAdvanced setup
🧩 One of 7 skills in the samber/cc-skills-golang package — works on its own, and pairs well with its siblings.

Golang application framework using uber-go/fx — fx.New, fx.Provide, fx.Invoke, fx.Module, fx.Lifecycle hooks, fx.Annotate (name/group/As), fx.Decorate, fx.Supply, fx.Replace, fx.WithLogger, and signal-aware Run(). Apply when using or adopting uber-go/fx, when the codebase imports `go.uber.org/fx`, or when wiring services with fx.New. For raw DI without lifecycle, see `samber/cc-skills-golang@golang-uber-dig` skill.

Inspect the full instructions your agent will receiveExpand

This is the exact playbook injected into your agent when the skill activates — shown here so you can audit it before installing. You don't need to read it to use the skill.

by samber

Golang application framework using uber-go/fx — fx.New, fx.Provide, fx.Invoke, fx.Module, fx.Lifecycle hooks, fx.Annotate (name/group/As), fx.Decorate, fx.Supply, fx.Replace, fx.WithLogger, and signal-aware Run(). Apply when using or adopting uber-go/fx, when the codebase imports go.uber.org/fx, or when wiring services with fx.New. For raw DI without lifecycle, see samber/cc-skills-golang@golang-uber-dig skill. npx skills add https://github.com/samber/cc-skills-golang --skill golang-uber-fx Download ZIPGitHub2.4k 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. For Go package docs, versions, symbols, and known vulnerabilities, → See samber/cc-skills-golang@golang-pkg-go-dev skill.

Copy & paste — that's it
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

Copy & paste — that's it
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

Copy & paste — that's it
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.

Copy & paste — that's it
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):

Copy & paste — that's it
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.

Copy & paste — that's it
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:

Copy & paste — that's it
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:

Copy & paste — that's it
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.

Copy & paste — that's it
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.

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.