Mastering Advanced fx Features: Invoke, Supply, Populate, and More

This article dives deep into Uber's fx dependency‑injection framework for Go, explaining advanced constructs such as fx.Invoke, fx.Supply, fx.Populate, fx.Annotated, fx.In, fx.Out, fx.Module, fx.WithLogger and fx.As, and provides complete code examples that illustrate how to manage lifecycles, inject static values, differentiate multiple implementations, and customize logging in production‑grade applications.

FunTester
FunTester
FunTester
Mastering Advanced fx Features: Invoke, Supply, Populate, and More

fx.Invoke

The fx.Invoke option registers functions that are executed when the application starts. These functions automatically receive any required dependencies from the fx container, similar to Spring Boot's CommandLineRunner but with lazy initialization. The example creates a simple main that provides an Age struct, a zap.Logger, and a Tester struct, then registers an anonymous function via fx.Invoke to ensure the Tester (and its dependencies) are instantiated at startup.

package main

import (
    "go.uber.org/fx"
    "go.uber.org/zap"
)

type Age struct { Num int }

type Tester struct { Log *zap.Logger; Age *Age }

func NewTester(age *Age, log *zap.Logger) *Tester { return &Tester{Age: age, Log: log} }

func main() {
    app := fx.New(
        fx.Provide(
            NewTester,
            func() *Age { return &Age{Num: 18} },
            func() (*zap.Logger, error) { return zap.NewProduction() },
        ),
        fx.Invoke(func(*Tester) {}),
    )
    app.Run()
}

fx.Supply

fx.Supply

injects a concrete value directly into the container without needing a provider function. Typical scenarios include supplying static configuration objects, pre‑created instances, or test doubles.

package main

import (
    "go.uber.org/fx"
    "go.uber.org/zap"
)

type Age struct { Num int }

type Tester struct { Log *zap.Logger; Age *Age }

func NewTester(age *Age, log *zap.Logger) *Tester { return &Tester{Age: age, Log: log} }

func main() {
    app := fx.New(
        fx.Provide(func() (*zap.Logger, error) { return zap.NewProduction() }),
        fx.Supply(&Age{Num: 18}),
    )
    app.Run()
}

fx.Populate

fx.Populate

copies values from the fx container into external variables, which is useful for global variables or testing setups. The targets must be pointers to the concrete types.

package main

import (
    "go.uber.org/fx"
    "go.uber.org/zap"
)

var (
    logger *zap.Logger
    age    *Age
)

func main() {
    app := fx.New(
        fx.Provide(NewLogger, NewAge),
        fx.Populate(&logger, &age),
        fx.Invoke(func() { logger.Info("Application started", zap.Int("age", age.Num)) }),
    )
    app.Run()
}

func NewLogger() (*zap.Logger, error) { return zap.NewProduction() }
func NewAge() *Age { return &Age{Num: 30} }

type Age struct { Num int }

fx.Annotated

fx.Annotated

(used together with fx.Annotate) lets you tag constructors so that multiple providers of the same type can be distinguished, for example providing an "old" and a "young" Age implementation.

package main

import (
    "go.uber.org/fx"
    "go.uber.org/zap"
)

type Age struct { Num int }

type Tester struct { Log *zap.Logger; Age *Age }

var Ages = fx.Provide(
    fx.Annotated{ Name: "old",   Target: NewAgeOld },
    fx.Annotated{ Name: "young", Target: NewAgeYoung },
)

func main() {
    app := fx.New(
        fx.Provide(NewLogger),
        fx.Annotate(NewTester, fx.ParamTags(`name:"old"`)),
        Ages,
        fx.Invoke(func(t *Tester) { t.Log.Info("success", zap.Int("age", t.Age.Num)) }),
    )
    app.Run()
}

func NewLogger() (*zap.Logger, error) { return zap.NewProduction() }
func NewTester(age *Age, log *zap.Logger) *Tester { return &Tester{Age: age, Log: log} }
func NewAgeYoung() *Age { return &Age{Num: 18} }
func NewAgeOld() *Age { return &Age{Num: 60} }

fx.In

fx.In

aggregates several dependencies into a single struct, allowing optional fields, tag‑based selection, and cleaner constructor signatures.

package main

import (
    "fmt"
    "go.uber.org/fx"
)

type Name struct { Str string }

type Age struct { Num int }

type Tester struct {
    fx.In
    Age  *Age
    Name *Name
}

func main() {
    app := fx.New(
        fx.Provide(
            func() *Age { return &Age{Num: 30} },
            func() *Name { return &Name{Str: "FunTester"} },
        ),
        fx.Invoke(func(t Tester) { fmt.Println(t.Name.Str) }),
    )
    app.Run()
}

fx.Out

fx.Out

enables a constructor to return multiple values that the container can inject elsewhere, optionally using tags to name or group them.

package main

import (
    "fmt"
    "go.uber.org/fx"
)

type Name struct { Str string }

type Age struct { Num int }

type Values struct {
    fx.Out
    Age  *Age
    Name *Name
}

func NewValues() Values { return Values{Age: &Age{Num: 30}, Name: &Name{Str: "FunTester"}} }

func main() {
    app := fx.New(
        fx.Provide(NewValues),
        fx.Invoke(func(age *Age) { fmt.Println(age.Num) }),
    )
    app.Run()
}

fx.Module

fx.Module

groups related providers and invokers into a reusable unit that can be nested or conditionally loaded, simplifying the structure of large Go applications.

fx.WithLogger

fx.WithLogger

lets you replace the default fx logger with a custom implementation such as zap.Logger, providing consistent logging across the application.

fx.As

fx.As

converts a concrete type to an interface during fx.Provide, allowing more flexible and testable dependency injection.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backend DevelopmentGodependency-injectionUber fxfx.Annotatedfx.Invokefx.Supply
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.