golang-uber-fx

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,…

INSTALLATION
npx skills add https://github.com/samber/cc-skills-golang --skill golang-uber-fx
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$27

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.

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card