
Golang Uber Fx
- 31.6k installs
- 2.8k repo stars
- Updated July 27, 2026
- samber/cc-skills-golang
uber-go/fx is a Go application framework for DI, lifecycle management, and modular service composition.
About
uber-go/fx is a Go application framework that combines dependency injection with lifecycle management and signal handling. It uses reflection-based wiring like dig but adds OnStart/OnStop hooks, module composition, and a blocking run loop for services. Developers use it when building long-running services like HTTP servers and workers that need coordinated startup and shutdown.
- Lifecycle hooks with OnStart/OnStop for graceful shutdown
- Module system for composable service wiring
- Signal-aware run loop that blocks on SIGINT/SIGTERM
Golang Uber Fx by the numbers
- 31,582 all-time installs (skills.sh)
- +412 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #32 of 4,386 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
golang-uber-fx capabilities & compatibility
- Capabilities
- di wiring · lifecycle management · module composition
- Platforms
- macOS · Windows · Linux
- Runs
- Runs locally
- Pricing
- Free
npx skills add https://github.com/samber/cc-skills-golang --skill golang-uber-fxAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 31.6k |
|---|---|
| repo stars | ★ 2.8k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | samber/cc-skills-golang ↗ |
How do you wire Go services with Uber Fx?
Wire and manage long-running Go services with dependency injection, lifecycle hooks, and graceful shutdown.
Who is it for?
Long-running services needing DI, lifecycle hooks, and coordinated startup/shutdown
Skip if: CLI tools or simple scripts where DI overhead is unnecessary
When should I use this skill?
Building HTTP services, workers, or daemons with complex dependency graphs
What you get
Fx application modules with providers, invokes, lifecycle hooks, and signal-aware Run() startup.
- Fx application bootstrap code
- Modular fx.Module provider sets
By the numbers
- Skill version 1.1.0 from samber/cc-skills-golang
Files
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.
go get go.uber.org/fxfx 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 hooksBoot 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 pairParameter 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
1. Keep main() thin — providers, modules, and a single Run(). Push real work into modules so each can be tested in isolation. 2. 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. 3. OnStart must return promptly — long work goes in a goroutine inside the hook. A blocking OnStart hangs the rest of the boot. 4. Respect ctx.Done() in hooks — a hook that ignores cancellation is reported as a timeout failure but its goroutine continues, leaking resources. 5. Group by module, not by layer — a module owns the providers, lifecycle, and decorators for one concern (HTTP, DB, metrics). 6. Use fx.Annotate for tags rather than wrapping a constructor in an fx.Out struct — keeps the constructor reusable outside fx. 7. Replace fx.Provide with fx.Supply for pre-built values (config, command-line flags). Shorter, signals intent. 8. 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 <https://github.com/uber-go/fx/issues>.
[
{
"id": 1,
"name": "lifecycle-not-init",
"description": "Tests use of fx.Lifecycle hooks instead of init() or constructor side effects for startup work",
"prompt": "In my Go service using uber-go/fx, I have a NewHTTPServer constructor that should listen on a port and serve requests. Where should I call srv.Serve(ln) — inside the constructor, in init(), or somewhere else?",
"trap": "Without the skill, the model calls srv.Serve in init() or directly inside the constructor (which would block boot), or writes a goroutine inside the constructor (which fires before lifecycle ordering applies). The right answer is OnStart with a goroutine.",
"assertions": [
{"id": "1.1", "text": "Injects fx.Lifecycle into NewHTTPServer"},
{"id": "1.2", "text": "Calls lc.Append with an fx.Hook (or fx.StartHook/StopHook) — OnStart starts the server, OnStop calls Shutdown"},
{"id": "1.3", "text": "OnStart launches srv.Serve inside a goroutine so the hook returns quickly"},
{"id": "1.4", "text": "Does NOT call srv.Serve directly inside the constructor"},
{"id": "1.5", "text": "Does NOT use init() to start the server"},
{"id": "1.6", "text": "OnStop calls srv.Shutdown(ctx) for graceful shutdown"}
]
},
{
"id": 2,
"name": "annotate-vs-fxout-struct",
"description": "Tests fx.Annotate as the modern way to add tags or interface bindings",
"prompt": "I have NewPostgresDB returning *PostgresDB in my Go app using uber-go/fx. I want consumers to ask for a Database interface, and I want this DB tagged with name:\"primary\" so I can add a replica later. Show me how.",
"trap": "Without the skill, the model writes a separate adapter constructor returning Database, or wraps the result in an fx.Out struct — missing fx.Annotate(NewPostgresDB, fx.As(new(Database)), fx.ResultTags(...)) which does both in one line.",
"assertions": [
{"id": "2.1", "text": "Uses fx.Annotate around NewPostgresDB"},
{"id": "2.2", "text": "Uses fx.As(new(Database)) inside the annotation to bind the interface"},
{"id": "2.3", "text": "Uses fx.ResultTags(`name:\"primary\"`) for the named tag"},
{"id": "2.4", "text": "Does NOT introduce a separate adapter/wrapper constructor"},
{"id": "2.5", "text": "Does NOT rewrite NewPostgresDB to return Database directly (since the original constructor stays untouched)"}
]
},
{
"id": 3,
"name": "module-organization",
"description": "Tests fx.Module for organizing related providers/invokes/decorators",
"prompt": "My Go application using uber-go/fx is growing — main.go now has dozens of fx.Provide calls for HTTP, database, metrics, and worker concerns mixed together. How should I reorganize this?",
"trap": "Without the skill, the model splits providers across several Go packages but keeps a flat list in main(), missing fx.Module which groups providers, invokes, and decorators under a name and lets decorators be module-scoped.",
"assertions": [
{"id": "3.1", "text": "Recommends fx.Module to group related options"},
{"id": "3.2", "text": "Shows at least 2 separate modules (e.g., HTTPModule, DatabaseModule)"},
{"id": "3.3", "text": "main() composes the modules via fx.New(HTTPModule, DatabaseModule, ...)"},
{"id": "3.4", "text": "Mentions OR demonstrates that fx.Decorate inside a module is scoped to that module"},
{"id": "3.5", "text": "Each module includes its own fx.Provide (and possibly fx.Invoke / fx.Decorate) calls"}
]
},
{
"id": 4,
"name": "supply-vs-provide",
"description": "Tests fx.Supply for pre-built values (config, secrets) instead of fx.Provide with a no-op constructor",
"prompt": "In my Go application using uber-go/fx, I parse a *Config from flags and load an API_KEY environment variable in main() before calling fx.New. How should I make these available to the rest of the graph?",
"trap": "Without the skill, the model writes fx.Provide(func() *Config { return cfg }) — a redundant constructor that just returns the existing value. fx.Supply does this without the boilerplate.",
"assertions": [
{"id": "4.1", "text": "Uses fx.Supply(cfg) (or fx.Supply with both values)"},
{"id": "4.2", "text": "Does NOT wrap the pre-built values in fx.Provide(func() *Config { return cfg })"},
{"id": "4.3", "text": "Mentions or demonstrates that fx.Supply makes pre-built values first-class graph members"},
{"id": "4.4", "text": "If both values are supplied as the same type or need a tag, optionally uses fx.Annotate within fx.Supply for tagging"}
]
},
{
"id": 5,
"name": "fxtest-with-populate",
"description": "Tests fxtest.New + fx.Populate for testing instead of raw fx.New + fx.Invoke",
"prompt": "I have a *UserService wired in my Go app using uber-go/fx. I want a unit test that pulls *UserService out of the graph (with a fake Database injected) and asserts behavior. Show me the minimal test.",
"trap": "Without the skill, the model uses fx.New + fx.Invoke(func(s *UserService) { ... }) — works but doesn't fail the test cleanly and has no Cleanup integration. fxtest.New + fx.Populate is idiomatic.",
"assertions": [
{"id": "5.1", "text": "Uses fxtest.New(t, ...) instead of fx.New"},
{"id": "5.2", "text": "Uses fx.Populate(&svc) to extract the *UserService from the graph"},
{"id": "5.3", "text": "Calls app.RequireStart() (or .Start) and app.RequireStop() (e.g., as t.Cleanup or defer)"},
{"id": "5.4", "text": "Provides a fake Database (interface) — does NOT use the real DB"},
{"id": "5.5", "text": "Does NOT use fx.Invoke as the primary mechanism for extracting the service"}
]
},
{
"id": 6,
"name": "replace-for-fakes",
"description": "Tests fx.Replace inside fxtest to swap a real dependency embedded in a module",
"prompt": "My Go app uses uber-go/fx with a ProductionModule that bundles all real wiring. In one integration test, I want to replace the real Database with an erroring fake — without rewriting the module. How?",
"trap": "Without the skill, the model rewrites the module (or copies it) to inject the fake — missing fx.Replace which overrides a previously-provided type without touching the module.",
"assertions": [
{"id": "6.1", "text": "Uses fx.Replace(...) inside fxtest.New (or fx.New) to override the Database"},
{"id": "6.2", "text": "Composes ProductionModule alongside fx.Replace (the module is reused unchanged)"},
{"id": "6.3", "text": "Uses fx.Annotate inside fx.Replace if needed for fx.As(new(Database)) binding"},
{"id": "6.4", "text": "Does NOT modify or duplicate the production module to inject the fake"},
{"id": "6.5", "text": "Mentions that fx.Replace is appropriate for tests (not production code)"}
]
},
{
"id": 7,
"name": "value-groups-handlers",
"description": "Tests value groups when many handler constructors must contribute to one slice",
"prompt": "In my Go HTTP server using uber-go/fx, I want every NewXxxHandler constructor to register itself with the router automatically — no manual list of handlers in main(). I have NewUserHandler, NewPostHandler, NewHealthHandler. The router consumes []http.Handler. Wire this.",
"trap": "Without the skill, the model assembles a slice manually in main() or writes one constructor that builds all handlers — missing the group:\"...\" tag pattern that keeps producers and the consumer decoupled.",
"assertions": [
{"id": "7.1", "text": "Each handler is registered with fx.Annotate(... fx.ResultTags(`group:\"routes\"`)) (or via an fx.Out struct with a group tag)"},
{"id": "7.2", "text": "The router (or server) consumes a fx.In with []http.Handler tagged group:\"routes\""},
{"id": "7.3", "text": "Optionally uses fx.As(new(http.Handler)) inside the annotation if the constructor returns a concrete type"},
{"id": "7.4", "text": "Does NOT manually maintain a slice of handlers in main()"},
{"id": "7.5", "text": "Does not assert ordering of the resulting slice (or explicitly notes order is unspecified)"}
]
},
{
"id": 8,
"name": "logger-fxevent-zap",
"description": "Tests fx.WithLogger + fxevent.ZapLogger to route fx events through the app's structured logger",
"prompt": "My Go service using uber-go/fx logs everything through *zap.Logger. The default fx output goes to stderr in a different format and is noisy in production. How do I route fx's own events (provide/invoke/start/stop) through my zap logger?",
"trap": "Without the skill, the model suggests overriding os.Stderr or grepping logs — missing fx.WithLogger which lets you provide an fxevent.Logger backed by zap.",
"assertions": [
{"id": "8.1", "text": "Uses fx.WithLogger(...) as an fx.New option"},
{"id": "8.2", "text": "The provided fxevent.Logger is &fxevent.ZapLogger{Logger: log}"},
{"id": "8.3", "text": "The logger inside fx.WithLogger receives the *zap.Logger as a parameter (so fx wires it from the graph)"},
{"id": "8.4", "text": "Does NOT redirect stderr or modify global log output"},
{"id": "8.5", "text": "May mention fx.NopLogger as an option to silence fx events"}
]
},
{
"id": 9,
"name": "manual-lifecycle-cli",
"description": "Tests app.Start / app.Done / app.Stop for embedding fx in a larger program",
"prompt": "I'm building a Go CLI tool that has an interactive sub-command and a serve sub-command. I want the serve sub-command to spin up an fx graph, start it, wait for SIGINT, and shut down — but I don't want fx hijacking the entire process via app.Run() (because the CLI may resume to other work after). How do I drive fx manually?",
"trap": "Without the skill, the model calls app.Run() and then can't return to the CLI — missing manual Start/Done/Stop, which is exactly what the user is asking for.",
"assertions": [
{"id": "9.1", "text": "Uses app.Start(ctx) explicitly with a context (often timeout-bounded)"},
{"id": "9.2", "text": "Waits on app.Done() (or a select including parent context cancellation) instead of calling app.Run()"},
{"id": "9.3", "text": "Uses app.Stop(ctx) explicitly with a context"},
{"id": "9.4", "text": "Does NOT recommend app.Run() as the primary mechanism for this scenario"},
{"id": "9.5", "text": "Mentions that app.Err() can validate wiring without starting"}
]
},
{
"id": 10,
"name": "onstart-non-blocking",
"description": "Tests that long-running OnStart work is launched in a goroutine, not run synchronously",
"prompt": "In my Go app using uber-go/fx, the OnStart hook for a worker calls a method that runs forever (consuming jobs from a queue until the app stops). Show me the OnStart implementation.",
"trap": "Without the skill, the model calls the long-running method synchronously inside OnStart — which hangs startup. The right pattern is to spawn a goroutine and return nil quickly.",
"assertions": [
{"id": "10.1", "text": "OnStart launches the long-running method inside a goroutine"},
{"id": "10.2", "text": "OnStart itself returns nil (or an error) quickly without waiting for the worker to finish"},
{"id": "10.3", "text": "OnStop signals the worker to stop (closing a channel, calling Cancel, etc.) and waits for it to drain"},
{"id": "10.4", "text": "Does NOT call the long-running method synchronously inside OnStart"},
{"id": "10.5", "text": "Mentions that a blocking OnStart would hang the boot / dependent hooks"}
]
},
{
"id": 11,
"name": "fx-when-not-dig",
"description": "Tests recommending raw dig (instead of fx) when the user does not need lifecycle / app boot",
"prompt": "I'm writing a one-shot Go CLI command that builds a small object graph (parses input, creates a few services, calls one of them, exits). I'm reading about uber-go/fx but it seems heavy. Should I use fx for this?",
"trap": "Without the skill, the model unconditionally recommends fx — missing that for one-shot programs without lifecycle, raw uber-go/dig is the lighter, simpler choice.",
"assertions": [
{"id": "11.1", "text": "Recommends raw uber-go/dig (or notes fx is overkill for this case)"},
{"id": "11.2", "text": "Mentions that fx is built on dig — the wiring patterns are nearly identical"},
{"id": "11.3", "text": "Mentions that fx adds value when the program needs lifecycle hooks, signal handling, or modular composition"},
{"id": "11.4", "text": "Does NOT recommend introducing fx.Lifecycle/fx.Module to a one-shot program"},
{"id": "11.5", "text": "May recommend manual constructor injection if the graph is very small"}
]
}
]
Advanced — uber-go/fx
Detail topics referenced from SKILL.md. Each section is self-contained.
fx.Supply, fx.Replace, fx.Decorate
| Option | Purpose |
|---|---|
fx.Supply(values...) | Provide pre-built values directly. Use for config, secrets, parsed flags. |
fx.Replace(values...) | Replace an already-provided type. Most useful in tests: swap real for fake. |
fx.Decorate(fn) | Wrap or modify an existing value. Scoped to the surrounding module. |
fx.Supply(cfg, secret)
// Replace inside fxtest
fx.Replace(fx.Annotate(&fakeDB{}, fx.As(new(Database))))
// Decorate, module-scoped
fx.Module("worker",
fx.Decorate(func(s metrics.Scope) metrics.Scope {
return s.Tagged(map[string]string{"component": "worker"})
}),
)Optional Dependencies
optional:"true" lets a consumer compile and run when no provider exists. Use it for genuinely optional features (a tracer, a cache) — not for core services like a database.
type Params struct {
fx.In
Logger *zap.Logger
Tracer trace.Tracer `optional:"true"`
}Logging fx Events
fx emits structured events (provide, invoke, hook execution, errors) through fxevent.Logger. By default it writes to stderr — replace with a Zap logger or silence it in tests:
fx.New(
fx.Provide(NewZapLogger),
fx.WithLogger(func(log *zap.Logger) fxevent.Logger {
return &fxevent.ZapLogger{Logger: log}
}),
// Or silence: fx.NopLogger
)Manual Lifecycle Control
app.Run() is convenient but inflexible. For tests, custom signal handling, or embedding fx in a larger program, drive the lifecycle manually:
app := fx.New(/* ... */)
startCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil {
log.Fatal(err)
}
<-app.Done() // waits for SIGINT/SIGTERM
stopCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := app.Stop(stopCtx); err != nil {
log.Fatal(err)
}fx.StartTimeout and fx.StopTimeout set defaults; pass an explicit context to override per-call.
Quick Reference
Application
| Function | Purpose |
|---|---|
fx.New(opts...) | Build the application graph |
app.Run() | Start, wait for signal, Stop — single call |
app.Start(ctx) | Run OnStart hooks in dependency order |
app.Stop(ctx) | Run OnStop hooks in reverse order |
app.Done() | Channel that closes on SIGINT/SIGTERM |
app.Err() | Wiring error from fx.New (validate without starting) |
Wiring
| Option | Purpose |
|---|---|
fx.Provide(ctors...) | Register constructors |
fx.Invoke(fns...) | Run functions during Start |
fx.Supply(values...) | Provide pre-built values |
fx.Replace(values...) | Replace previously-provided values (tests) |
fx.Decorate(fn) | Wrap an existing value (module-scoped) |
fx.Module(name, opts...) | Group providers/invokes/decorators |
fx.Options(opts...) | Bundle options into a single value |
fx.Populate(targets...) | Extract typed values from the graph (tests) |
Annotations
| Function | Purpose |
|---|---|
fx.Annotate(fn, opts...) | Tag/interface-wrap a constructor |
fx.ParamTags("...") | Tag parameters of an annotated constructor |
fx.ResultTags("...") | Tag results of an annotated constructor |
fx.As(new(I)) | Provide as one or more interfaces |
fx.From(types...) | Bind annotated parameters to specific provided types |
Lifecycle
| Helper | Purpose |
|---|---|
fx.Hook{OnStart, OnStop} | Full hook with context-aware callbacks |
fx.StartHook(fn) | Adapt a simple Start function |
fx.StopHook(fn) | Adapt a simple Stop function |
fx.StartStopHook(start, stop) | Pair of simple Start/Stop functions |
fx.StartTimeout(d), fx.StopTimeout(d) | Override default 15s lifecycle timeouts |
fx.ErrorHook(h) | Intercept lifecycle errors (e.g. failed OnStart) for alerting or cleanup |
Logging & Testing
| Helper | Purpose |
|---|---|
fx.WithLogger(fn) | Plug in a custom fxevent.Logger |
fx.NopLogger | Silence fx event logging |
fxevent.ZapLogger{Logger: log} | Bridge fx events into zap |
fxevent.SlogLogger{Logger: log} | Bridge fx events into log/slog |
fxtest.New(t, opts...) | App that fails the test on errors |
app.RequireStart(), app.RequireStop() | Start/Stop with t.Fatal on failure |
fxtest.NewLifecycle(t) | Standalone lifecycle for unit tests |
Recipes — uber-go/fx
End-to-end examples that go beyond the SKILL.md basics. Each recipe is self-contained and shows a real wiring problem.
Full HTTP service with database, metrics, and graceful shutdown
package main
import (
"context"
"database/sql"
"fmt"
"net"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
"go.uber.org/zap"
)
func main() {
fx.New(
fx.Provide(
NewConfig,
NewLogger,
NewDatabase,
NewMetricsRegistry,
),
DatabaseModule,
HTTPModule,
MetricsModule,
fx.WithLogger(func(log *zap.Logger) fxevent.Logger {
return &fxevent.ZapLogger{Logger: log}
}),
fx.StartTimeout(30 * time.Second),
fx.StopTimeout(30 * time.Second),
).Run()
}
var DatabaseModule = fx.Module("database",
fx.Provide(
NewUserRepository,
NewPostRepository,
),
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("db")
}),
)
var HTTPModule = fx.Module("http",
fx.Provide(
NewRouter,
NewHTTPServer,
// Each handler joins the "routes" group.
AsRoute(NewUserHandler),
AsRoute(NewPostHandler),
AsRoute(NewHealthHandler),
),
fx.Invoke(func(*http.Server) {}), // forces server to be built
)
var MetricsModule = fx.Module("metrics",
fx.Provide(NewPrometheusHandler),
fx.Invoke(RegisterMetrics),
)
// Helper to register a handler with the "routes" group.
func AsRoute(ctor any) any {
return fx.Annotate(
ctor,
fx.As(new(Route)),
fx.ResultTags(`group:"routes"`),
)
}
type Route interface {
Pattern() string
http.Handler
}
type RouterParams struct {
fx.In
Routes []Route `group:"routes"`
}
func NewRouter(p RouterParams) *http.ServeMux {
mux := http.NewServeMux()
for _, r := range p.Routes {
mux.Handle(r.Pattern(), r)
}
return mux
}
func NewHTTPServer(lc fx.Lifecycle, log *zap.Logger, mux *http.ServeMux, cfg *Config) *http.Server {
srv := &http.Server{
Addr: cfg.Addr,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
ln, err := net.Listen("tcp", srv.Addr)
if err != nil {
return fmt.Errorf("listen %s: %w", srv.Addr, err)
}
go func() {
if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed {
log.Error("server error", zap.Error(err))
}
}()
log.Info("listening", zap.String("addr", srv.Addr))
return nil
},
OnStop: func(ctx context.Context) error {
log.Info("shutting down")
return srv.Shutdown(ctx)
},
})
return srv
}Background worker with graceful drain
type Worker struct {
log *zap.Logger
queue chan Job
done chan struct{}
}
func NewWorker(lc fx.Lifecycle, log *zap.Logger) *Worker {
w := &Worker{
log: log,
queue: make(chan Job, 100),
done: make(chan struct{}),
}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
go w.run()
return nil
},
OnStop: func(ctx context.Context) error {
close(w.queue) // signal "no more jobs"
select {
case <-w.done:
w.log.Info("worker drained cleanly")
return nil
case <-ctx.Done():
w.log.Warn("worker stop timeout")
return ctx.Err()
}
},
})
return w
}
func (w *Worker) run() {
defer close(w.done)
for job := range w.queue {
job.Do(w.log)
}
}The worker honors the stop context — under a 30-second fx.StopTimeout it has 30 seconds to drain. Beyond that, fx reports the timeout and the process exits.
Multiple implementations of the same interface
Use named annotations + fx.As to register two Cache implementations and inject them by name:
fx.Provide(
fx.Annotate(
NewRedisCache,
fx.As(new(Cache)),
fx.ResultTags(`name:"redis"`),
),
fx.Annotate(
NewMemcachedCache,
fx.As(new(Cache)),
fx.ResultTags(`name:"memcached"`),
),
)
type ServiceParams struct {
fx.In
Primary Cache `name:"redis"`
Fallback Cache `name:"memcached"`
}fx.Supply for config and secrets
func main() {
cfg := mustLoadConfig() // parsed flags + env, before fx
secret := os.Getenv("API_KEY")
fx.New(
fx.Supply(cfg), // *Config available everywhere
fx.Supply(fx.Annotate(secret, fx.ResultTags(`name:"apikey"`))),
fx.Provide(NewLogger, NewAPIClient),
fx.Invoke(run),
).Run()
}
func NewAPIClient(cfg *Config, p struct {
fx.In
APIKey string `name:"apikey"`
}) *APIClient {
return &APIClient{baseURL: cfg.APIBaseURL, key: p.APIKey}
}fx.Supply makes pre-built values first-class graph members. It is shorter and clearer than fx.Provide(func() *Config { return cfg }).
Module-scoped decorator
var WorkerModule = fx.Module("worker",
fx.Provide(NewWorker, NewJobQueue),
// Inside this module, *zap.Logger is automatically named "worker".
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("worker")
}),
)
var APIModule = fx.Module("api",
fx.Provide(NewServer, NewRouter),
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("api")
}),
)The two modules see different loggers — there is no shared mutation of the parent value.
Optional dependency for tracing
type ServerParams struct {
fx.In
Logger *zap.Logger
Tracer trace.Tracer `optional:"true"`
}
func NewServer(p ServerParams) *Server {
s := &Server{log: p.Logger}
if p.Tracer == nil {
s.tracer = trace.NewNoopTracerProvider().Tracer("noop")
} else {
s.tracer = p.Tracer
}
return s
}Reach for optional only when the dependency is genuinely optional. A missing core service hidden behind optional becomes a nil-pointer panic at first use.
Manual lifecycle for embedding fx in a CLI
When fx is one component inside a larger program (a CLI tool, a test runner), drive Start/Stop yourself instead of calling Run():
func runFxApp(parent context.Context) error {
app := fx.New(
fx.Provide(NewConfig, NewLogger, NewWorker),
fx.Invoke(func(*Worker) {}),
)
if err := app.Err(); err != nil {
return fmt.Errorf("wire: %w", err)
}
startCtx, cancel := context.WithTimeout(parent, 30*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil {
return fmt.Errorf("start: %w", err)
}
select {
case <-parent.Done():
case <-app.Done(): // SIGINT/SIGTERM
}
stopCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return app.Stop(stopCtx)
}app.Err() validates wiring without starting — useful for --check style flags.
Custom event logger that filters noise
type ProductionLogger struct {
inner *fxevent.ZapLogger
}
func (l *ProductionLogger) LogEvent(e fxevent.Event) {
switch e.(type) {
case *fxevent.Provided, *fxevent.Supplied, *fxevent.Decorated:
return // drop the per-Provide chatter
default:
l.inner.LogEvent(e)
}
}
fx.New(
fx.Provide(NewZapLogger),
fx.WithLogger(func(log *zap.Logger) fxevent.Logger {
return &ProductionLogger{inner: &fxevent.ZapLogger{Logger: log}}
}),
)In production, filtering provide/decorate noise leaves only lifecycle (start/stop) events and errors — much easier to audit.
Testing with uber-go/fx
go.uber.org/fx/fxtest integrates fx applications with *testing.T: errors fail the test instead of crashing the process, and lifecycle teardown is registered automatically.
Pulling a value out of the graph with fx.Populate
func TestUserService_Create(t *testing.T) {
var svc *UserService
app := fxtest.New(t,
fx.Provide(
func() Database { return &fakeDatabase{} },
NewUserService,
),
fx.Populate(&svc),
)
defer app.RequireStop()
app.RequireStart()
require.NoError(t, svc.Create(context.Background(), "alice@example.com"))
}fx.Populate(&svc) fills svc with the value the graph would resolve. It replaces ad-hoc fx.Invoke(func(s *UserService) { svc = s }) patterns.
fx.Replace to swap a real dependency for a fake
func TestServer_HandlesDBError(t *testing.T) {
var srv *http.Server
fakeDB := &erroringDatabase{}
app := fxtest.New(t,
ProductionModule, // the real wiring
fx.Replace(fx.Annotate(fakeDB, fx.As(new(Database)))),
fx.Populate(&srv),
)
defer app.RequireStop()
app.RequireStart()
// Drive the server with a fake DB
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/users", nil)
srv.Handler.ServeHTTP(rec, req)
require.Equal(t, http.StatusInternalServerError, rec.Code)
}fx.Replace works even when the original provider is buried inside a module — it overrides the resolved type without rewriting the module.
Standalone lifecycle for a unit test
fxtest.NewLifecycle(t) gives you an fx.Lifecycle outside the fx.New machinery, useful for testing a single constructor that registers hooks:
func TestWorker_StartStop(t *testing.T) {
lc := fxtest.NewLifecycle(t)
worker := NewWorker(lc, zaptest.NewLogger(t))
require.NotNil(t, worker)
lc.RequireStart() // runs OnStart hooks
require.True(t, worker.IsRunning())
lc.RequireStop() // runs OnStop hooks
require.False(t, worker.IsRunning())
}This is the lightest test for a constructor — no full graph, no fx.New.
Asserting wire-time errors
func TestWiring_MissingDependency(t *testing.T) {
app := fx.New(
fx.Provide(NewServer), // depends on *sql.DB which is not provided
fx.NopLogger,
)
require.Error(t, app.Err())
require.Contains(t, app.Err().Error(), "missing type: *sql.DB")
}Use fx.New (not fxtest.New) when you _expect_ the wiring to fail — fxtest.New would call t.Fatal.
Validating the production graph in CI
func TestProductionGraph(t *testing.T) {
app := fx.New(
ProductionOptions(), // every fx.Provide / fx.Module the binary uses
fx.NopLogger,
)
require.NoError(t, app.Err())
}fx.New validates the type graph without starting. The test fails before deploy on any missing-provider, cycle, or annotation mismatch.
Test logger that captures fx events
When you want to assert on lifecycle behavior, route fx events into an in-memory observer:
// go.uber.org/zap/zaptest/observer
core, recorded := observer.New(zap.InfoLevel)
log := zap.New(core)
app := fxtest.New(t,
fx.WithLogger(func() fxevent.Logger {
return &fxevent.ZapLogger{Logger: log}
}),
fx.Provide(NewWorker),
fx.Invoke(func(*Worker) {}),
)
defer app.RequireStop()
app.RequireStart()
require.NotEmpty(t, recorded.FilterMessage("OnStart hook executed").All())Testing a lifecycle hook in isolation
If a constructor returns a value _and_ registers a hook, you often want to test both halves:
func TestNewServer_OnStartFailsBindError(t *testing.T) {
// Bind a port so :0 is unavailable... no, simpler: pre-bind and pass that addr
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
defer listener.Close()
addr := listener.Addr().String()
cfg := &Config{Addr: addr}
lc := fxtest.NewLifecycle(t)
NewHTTPServer(lc, zaptest.NewLogger(t), cfg)
// Use Start directly (not RequireStart) so we can assert the error.
require.Error(t, lc.Start(context.Background()))
}Related skills
FAQ
What Uber Fx APIs does golang-uber-fx cover?
golang-uber-fx v1.1.0 covers fx.New, fx.Provide, fx.Invoke, fx.Module, fx.Lifecycle hooks, fx.Annotate, fx.Decorate, fx.Supply, fx.Replace, fx.WithLogger, and signal-aware Run() for go.uber.org/fx service wiring.
When should developers pick golang-uber-fx over golang-uber-dig?
Developers should pick golang-uber-fx when they need uber-go/fx lifecycle management, modular fx.Module composition, and graceful startup/shutdown. golang-uber-dig covers raw dig DI without Fx lifecycle orchestration.
Is Golang Uber Fx safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.