Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
31 Aug 2025 · 6 min read ·Article 62 / 125
Go

62 Resolvers with Dependency Injection in Go

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

62 Resolvers with Dependency Injection in Go

Dependency Injection (DI) in Go is an essential practice that supports testing, scalability, and separation of concerns. However, integrating DI techniques with dependency providers—such as resolvers in GraphQL—is still often confusing, especially for Go engineers used to a monolithic code structure. In this article, I’ll cover how to apply 62 resolvers with Dependency Injection in Go, break down the concept, explain best practices, and round it out with code examples, diagrams, and simulation tables. Let’s get started.


What Is a Resolver in the Context of Go?

In general, a resolver refers to a function/factory that “resolves” runtime dependencies while the code is running. The clearest real-world example shows up in GraphQL applications in Go, for instance using gqlgen . A resolver represents the layer between a GraphQL query and the business logic implementation, often depending on services, repositories, or other components.

Danger
The number 62 in the title refers to the number of resolvers we’re simulating at a medium-to-large project scale (e.g., 62 endpoints/service logic that need to be “resolved”).

The Challenge of Managing 62 Resolvers Manually

The main problems when managing many resolvers in Go:

  1. Boilerplate code duplication: Injecting dependencies manually into every resolver.
  2. Hard to maintain: Each time you add a new dependency, you have to change the constructor of every resolver.
  3. Harder to test: If DI isn’t consistent, mocking and testing become complicated.

Basic Dependency Injection in Go

Go isn’t Java or .NET, which have a native DI container. Even so, the dependency injection practice can still be applied using the principles of constructor injection or interface injection.

Here’s the most basic pattern:

go
 1type UserService interface {
 2    GetUser(id string) (*User, error)
 3}
 4
 5type UserResolver struct {
 6    Service UserService
 7}
 8
 9func NewUserResolver(svc UserService) *UserResolver {
10    return &UserResolver{Service: svc}
11}

For 62 resolvers, without proper DI:

go
1var postResolver   = NewPostResolver(postService)
2var commentResolver = NewCommentResolver(commentService)
3var friendResolver  = NewFriendResolver(friendService)
4// and so on for all 62 resolvers...

This is too verbose and error-prone.


Building an Effective Dependency Injection Layer

Popular strategies for resolver injection in Go:

  • Manual wiring — Directly wiring up dependencies in main.go or the composition root
  • Using a DI container — Third-party libraries such as uber-go/dig

Let’s look at both.

1. Manual Wiring

The simplest and most “Go idiomatic” approach. The following table compares manual wiring and a container:

CriteriaManual WiringDI Container (dig)
BoilerplateQuite a lotMinimal
Compile-time SafetyHighMedium
Easy RefactoringDependsEasy
Learning CurveLowMedium

Manual Wiring Example (Constructor Injection)

Suppose we have 3 services and want to extend to 62 resolvers (the code below shows the basic principle):

go
 1type AppResolvers struct {
 2    UserResolver    *UserResolver
 3    PostResolver    *PostResolver
 4    CommentResolver *CommentResolver
 5    // ... up to 62 resolvers
 6}
 7
 8func NewAppResolvers(db *sql.DB) *AppResolvers {
 9    userService := NewUserService(db)
10    postService := NewPostService(db)
11    commentService := NewCommentService(db)
12
13    return &AppResolvers{
14        UserResolver:    NewUserResolver(userService),
15        PostResolver:    NewPostResolver(postService),
16        CommentResolver: NewCommentResolver(commentService),
17    }
18}

If the dependency chain gets deeper (e.g., a service depends on a repository, which depends on a cache, and so on), the wiring grows longer and longer.


2. Dependency Injection Container with Uber-dig

If the number of resolvers reaches the dozens, a library like Uber Dig can be very helpful. dig simplifies building the dependency graph and resolves dependencies automatically.

Installation

shell
1go get go.uber.org/dig

Implementation with Uber-dig: Resolver Simulation

Suppose we have 3 resolvers out of a total of 62 (for brevity):

go
 1import (
 2    "database/sql"
 3    "go.uber.org/dig"
 4)
 5
 6type UserService struct{ db *sql.DB }
 7type PostService struct{ db *sql.DB }
 8type CommentService struct{ db *sql.DB }
 9
10func NewUserService(db *sql.DB) *UserService      { return &UserService{db} }
11func NewPostService(db *sql.DB) *PostService      { return &PostService{db} }
12func NewCommentService(db *sql.DB) *CommentService{ return &CommentService{db} }
13
14type UserResolver struct{ Service *UserService }
15type PostResolver struct{ Service *PostService }
16type CommentResolver struct{ Service *CommentService }
17
18func NewUserResolver(svc *UserService) *UserResolver       { return &UserResolver{svc} }
19func NewPostResolver(svc *PostService) *PostResolver       { return &PostResolver{svc} }
20func NewCommentResolver(svc *CommentService) *CommentResolver { return &CommentResolver{svc} }
21
22func main() {
23    container := dig.New()
24    container.Provide(sql.Open) // supply *sql.DB
25    container.Provide(NewUserService)
26    container.Provide(NewPostService)
27    container.Provide(NewCommentService)
28    container.Provide(NewUserResolver)
29    container.Provide(NewPostResolver)
30    container.Provide(NewCommentResolver)
31
32    // resolve all resolvers
33    container.Invoke(func(
34        userResolver *UserResolver,
35        postResolver *PostResolver,
36        commentResolver *CommentResolver,
37    ) {
38        // can be injected into a router, GraphQL server, etc.
39    })
40}

Scalability: Just add a .Provide() for each new resolver/service. Uber-dig untangles the dependency graph and injects everything automatically, even when there are hundreds of them.


Dependency Injection Flow Diagram

Let’s look at an overview of resolver wiring with DI using mermaid:

MERMAID
graph TD
  subgraph Database Layer
    DB[(Database)]
  end

  subgraph Service Layer
    USV[UserService]
    PSV[PostService]
    CSV[CommentService]
  end

  subgraph Resolver Layer
    UR[UserResolver]
    PR[PostResolver]
    CR[CommentResolver]
  end

  DB --> USV
  DB --> PSV
  DB --> CSV

  USV --> UR
  PSV --> PR
  CSV --> CR

  %% And so on, up to 62 resolvers

Imagine the diagram above growing to 62 nodes in the Service & Resolver layers—without a DI container, the wiring is extremely prone to errors.


Simulation Study: Adding the 63rd Resolver

Without a DI Container

  • Modify the AppResolvers constructor
  • Add the dependency in main.go
  • Add/match it in each service and resolver
  • High risk of error

With a DI Container

  • Add .Provide(NewNewResolver) to the container
  • The implementation stays consistent
  • No need to modify existing code

Simulation Table for Adding a Dependency:

DependencyManual Wiring (Steps)uber-dig (Steps)
Code in the Resolver11
Code in AppResolvers10
Code in main.go2-31
Human Error RiskMediumLow

Testing Resolvers with Dependency Injection

The main benefit of DI: easier testing! We can mock services, inject dependencies into resolvers, and isolate unit tests.

go
1func TestUserResolver_GetUser(t *testing.T) {
2    mockService := &MockUserService{}
3    resolver := NewUserResolver(mockService)
4    // continue with the test
5}

Conclusion

Managing 62+ resolvers in a Go project is a serious challenge if dependencies aren’t designed well. Dependency Injection—whether manual or using a container like Uber-dig—is a scalable, testable, and maintainable solution. Even though Go doesn’t have a built-in DI container, the constructor injection pattern and adopting an external library provide great flexibility for handling complex codebases.

Make DI your new standard when building large-scale Go projects—whether for GraphQL resolvers, REST handlers, or any other service logic!


References:


Hopefully this article helps you build scalable resolvers in Go!
Have fun coding 🚀

Related Articles

💬 Comments