Context & Why
When bootstrapping a new Go backend, wiring structs manually is simple. You instantiate a database pool, pass it to a repository, wrap that in a service, and register your HTTP handlers. But as that service expands to 25 or 30 dependencies, main.go quickly devolves into an unwieldy 300-line tangle. One constructor change forces cascading edits across dozens of call sites.
Ecosystems like Java (Spring) or Node.js (NestJS) tackle this with runtime reflection and service locators. Go developers often reach for reflection-based tools as well, most notably Uber’s fx and dig. They work, but runtime DI introduces a nasty failure mode: missing dependencies compile cleanly, only to panic at boot when deployed to staging or production.
Google Wire takes a fundamentally different path. Wire is a code generator, not a runtime container. It parses standard Go constructors at compile time and outputs explicit, idiomatic Go code that connects your components. If you introduce a circular dependency or forget a parameter, compilation halts immediately on your machine. Catching broken dependencies during a 2-second local build beats diagnosing a midnight startup crash in Kubernetes.
Installation
Wire consists of two components: a runtime library providing helper types like wire.Build, and a CLI tool that parses your code to generate injectors.
Start by initializing a Go module if you do not have an existing workspace:
mkdir go-wire-demo && cd go-wire-demo
go mod init github.com/example/go-wire-demo
Install the Wire code-generation CLI into your Go bin directory:
go install github.com/google/wire/cmd/wire@latest
Make sure your shell can find Go binaries by appending $GOPATH/bin to your system PATH:
export PATH=$PATH:$(go env GOPATH)/bin
wire help
Finally, add Wire’s runtime package to go.mod:
go get github.com/google/wire@latest
Configuration
Wire revolves around two basic primitives:
- Providers: Ordinary Go constructor functions that return values and optional errors (for example,
NewUserRepository(cfg *Config) (*UserRepository, error)). - Injectors: Function declarations where you invoke
wire.Build(...)to specify which target struct Wire must assemble.
1. Setting Up Domain Components
Create a small service containing a configuration struct, a user repository, and a notification service. Save this code to app.go:
package main
import (
"errors"
"fmt"
)
type Config struct {
DatabaseURL string
}
func NewConfig() *Config {
return &Config{
DatabaseURL: "postgres://user:secret@localhost:5432/production_db",
}
}
type UserRepository struct {
Config *Config
}
func NewUserRepository(cfg *Config) (*UserRepository, error) {
if cfg.DatabaseURL == "" {
return nil, errors.New("database URL cannot be empty")
}
return &UserRepository{Config: cfg}, nil
}
type NotificationService struct {
Repo *UserRepository
}
func NewNotificationService(repo *UserRepository) *NotificationService {
return &NotificationService{Repo: repo}
}
type App struct {
Notifier *NotificationService
}
func NewApp(notifier *NotificationService) *App {
return &App{Notifier: notifier}
}
func (a *App) Start() {
fmt.Printf("Application running with connected DB: %s\n", a.Notifier.Repo.Config.DatabaseURL)
}
2. Writing the Wire Injector Definition
Next, define the injector signature in wire.go. Note the build tag on line 1:
//go:build wireinject
// +build wireinject
package main
import (
"github.com/google/wire"
)
func InitializeApp() (*App, error) {
wire.Build(
NewConfig,
NewUserRepository,
NewNotificationService,
NewApp,
)
return nil, nil
}
The //go:build wireinject constraint tells the standard Go compiler to ignore this file during regular builds. Returning nil, nil is just a placeholder to satisfy the compiler; Wire replaces this function body completely during code generation.
3. Organizing with Provider Sets
Once a project grows past 10 or 15 constructors, passing every function into a single wire.Build call becomes messy. Group related constructors together using wire.NewSet:
// In a shared package or wire.go
var ServiceSet = wire.NewSet(
NewConfig,
NewUserRepository,
NewNotificationService,
)
You can then pass ServiceSet directly to your injector alongside root dependencies:
func InitializeApp() (*App, error) {
wire.Build(ServiceSet, NewApp)
return nil, nil
}
4. Generating the Injection Code
Run the wire command in your project directory:
wire
Wire inspects the constructor graph, matches return types to parameters, and generates wire_gen.go:
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package main
func InitializeApp() (*App, error) {
config := NewConfig()
userRepository, err := NewUserRepository(config)
if err != nil {
return nil, err
}
notificationService := NewNotificationService(userRepository)
app := NewApp(notificationService)
return app, nil
}
The output is plain Go. It orders constructor calls correctly, handles error bubbling, and adds zero reflection overhead to runtime execution.
5. Writing the Entry Point
With code generation complete, wire your entry point in main.go:
package main
import (
"log"
)
func main() {
app, err := InitializeApp()
if err != nil {
log.Fatalf("Failed to initialize application: %v", err)
}
app.Start()
}
Verification & Monitoring
Now verify that compile-time guarantees work as advertised.
1. Build and Run the Service
Execute the program with standard Go tooling:
go run .
The terminal confirms the entire tree initialized without manual wiring in main.go:
Application running with connected DB: postgres://user:secret@localhost:5432/production_db
2. Compile-Time Error Verification
To verify build-time safety, simulate a missing dependency. Comment out NewConfig in wire.go:
func InitializeApp() (*App, error) {
wire.Build(
// NewConfig removed
NewUserRepository,
NewNotificationService,
NewApp,
)
return nil, nil
}
Rerun the CLI:
wire
Generation fails instantly with a precise diagnostic:
wire: /path/to/go-wire-demo/wire.go:10:1: inject InitializeApp: no provider found for *main.Config
needed by *main.UserRepository in provider "NewUserRepository" (/path/to/go-wire-demo/app.go:21:1)
You find out about the misconfiguration in 50 milliseconds rather than discovering an uninitialized pointer crash in production.
3. CI Pipeline Integration
To prevent developers from committing stale or hand-edited wire_gen.go files, add a check to your continuous integration pipeline:
# Step in CI (e.g., GitHub Actions)
go install github.com/google/wire/cmd/wire@latest
wire ./...
git diff --exit-code wire_gen.go
The command runs code generation against committed source files. If anyone modifies a provider signature without running wire locally, git diff --exit-code flags the uncommitted change and blocks the pull request.

