Wire is a compile-time dependency injection framework for Go that generates dependency injection code, making manual DI feel like a relic of the past.

Let’s see Wire in action. Imagine we have a simple application with a Server that depends on a Greeter.

// greeter.go
package main

import "fmt"

type Greeter struct{}

func (g *Greeter) Greet(name string) string {
	return fmt.Sprintf("Hello, %s!", name)
}

// server.go
package main

type Server struct {
	greeter *Greeter
}

func NewServer(g *Greeter) *Server {
	return &Server{greeter: g}
}

func (s *Server) Run(name string) string {
	return s.greeter.Greet(name)
}

// main.go
package main

func main() {
	// Manual DI
	greeter := &Greeter{}
	server := NewServer(greeter)
	fmt.Println(server.Run("World"))

	// With Wire, this main.go would just call a generated main function
}

Manually constructing dependencies like this gets messy fast. As your application grows, so does the main function, becoming a tangled mess of NewSomething(...) calls. You have to manually track every constructor argument, every dependency. What if Greeter itself had a dependency? You’d need to update main to create that too. This is where Wire shines.

Wire works by analyzing a set of "provider" functions and a "щее" function. Provider functions are simply functions that create and return a dependency. The щее function (usually named Initialize or similar) tells Wire which top-level dependency you want and lists all the providers it needs, directly or indirectly.

Here’s how we’d set up Wire for our example:

// wire.go
//go:build wireinject
// +build wireinject

package main

import "github.com/google/wire"

func NewApp() *Server {
	wire.Build(NewServer, NewGreeter) // Tell Wire how to build Server and Greeter
	return &Server{} // The return type here is what Wire will try to construct
}

// NewGreeter is a provider function for Greeter
func NewGreeter() *Greeter {
	return &Greeter{}
}

Notice the //go:build wireinject directive. This tells the Go compiler to ignore this file during normal builds. When you run go generate, Wire will process this file.

To use Wire, you first need to install it: go install github.com/google/wire/cmd/wire@latest. Then, in your project’s root directory, run go generate. Wire will look for files with the wireinject build tag, find the wire.Build calls, and generate a new file (e.g., wire_gen.go) containing the actual Go code to construct your dependencies.

The generated wire_gen.go would look something like this (simplified):

// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//+build !wireinject

package main

// Injectors from wire.go:

// NewApp provides an instance of Server.
func NewApp() *Server {
	greeter := NewGreeter()
	server := NewServer(greeter)
	return server
}

// Providers from wire.go and *wire.go:

// NewGreeter returns a new Greeter.
func NewGreeter() *Greeter {
	return &Greeter{}
}

Now, your main.go becomes incredibly simple:

// main.go
package main

func main() {
	server := NewApp() // Wire generates this function
	fmt.Println(server.Run("Wire"))
}

The problem Wire solves is complexity management. It externalizes the dependency graph construction from your application’s core logic and your main function. Instead of manually weaving together hundreds of dependencies, you declare them once in wire.go, and Wire generates the efficient, correct wiring code. This makes refactoring easier, reduces boilerplate, and catches dependency errors at compile time.

The most surprising thing about Wire is how it handles cyclical dependencies. If A depends on B and B depends on A, Wire will correctly detect this during generation and report a compile-time error. It doesn’t just try to resolve them; it explicitly fails, preventing a runtime deadlock. This compile-time error detection is Wire’s core strength.

The exact levers you control with Wire are the provider functions and the wire.Build directives. Provider functions are just regular Go functions that accept dependencies as parameters and return the constructed type. You can use named functions, methods, or even anonymous functions. The wire.Build directive in your щее function is where you declare the set of all providers that Wire should consider for building your requested type. Wire will recursively analyze these providers and their dependencies to construct the complete graph.

What most people don’t realize is that Wire can also manage configuration values and even use wire.Value to inject pre-existing instances or configuration structs directly into your dependency graph without needing explicit provider functions for them. This is incredibly useful for passing down things like database connection strings or API keys without having to manually pass them through every single constructor.

The next concept you’ll likely encounter is how to handle more complex scenarios like factories, conditional dependencies, or integrating with third-party libraries that don’t expose constructors in a Wire-friendly way.

Want structured learning?

Take the full Golang course →