View : 207

08/05/2026 06:52am

JS2GO EP.47 Dependency Injection in Go and Node.js: Why Large Systems Need DI

JS2GO EP.47 Dependency Injection in Go and Node.js: Why Large Systems Need DI

#Clean Architecture

#Node.js

#Go

#Dependency Injection

Dependency Injection (DI) is one of the most important architectural techniques behind enterprise-level systems whether you’re building APIs, microservices, or large-scale platforms.

 

DI helps your codebase become:

✔ 10× easier to test (because every dependency can be mocked)
✔ Less complex with fewer bugs caused by global state
✔ Cleaner separation of concerns
✔ Flexible—you can swap implementations without breaking the system
✔ Maintainable, with a clear and predictable dependency graph

 

In simple terms: DI = Passing dependencies into a class/module/service instead of creating them inside.

 

⭐ 1) Why Does DI Make Systems So Much More Testable?

 

❌ Anti-pattern: Creating dependencies inside the service

type UserService struct {
    repo *UserRepository
}

func NewUserService() *UserService {
    return &UserService{
        repo: NewUserRepository(), // Hard-coded dependency
    }
}

 

🔥 Problems:

  • Cannot test without a real database
  • Cannot mock the repository
  • Forces a single implementation → tightly coupled
  • Hard to maintain and scale

 

✔ Correct pattern: Inject dependencies from outside

type UserService struct {
    repo UserRepository
}

func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

 

🎉 Benefits:

  • Easy unit testing (no real DB needed)
  • Swap implementations freely → pg → mongo → mock
  • Reduced coupling → much easier long-term maintenance

 

DI is the foundation of testable architecture.

 

⭐ 2) Constructor-based DI in Go (Production Standard)

 

Go does not require a DI framework.

 

Instead, the Go community embraces constructor injection, which is:

  • simple
  • explicit
  • debuggable
  • perfect for Clean Architecture

 

🎯 Interface = Dependency Contract

type UserRepository interface {
    FindByID(id string) (*User, error)
}

 

🎯 Implementation (PostgreSQL Repository)

type PgUserRepository struct {
    db *pgxpool.Pool
}

func (r *PgUserRepository) FindByID(id string) (*User, error) {
    var u User
    err := r.db.QueryRow(context.Background(),
        "SELECT id, name FROM users WHERE id=$1", id).
        Scan(&u.ID, &u.Name)
    return &u, err
}

 

🎯 Service Layer

type UserService struct {
    repo UserRepository
}

func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

 

🎯 Handler Layer

type UserHandler struct {
    service *UserService
}

func NewUserHandler(s *UserService) *UserHandler {
    return &UserHandler{s}
}

 

🎯 Build the Dependency Graph

func BuildUserModule(db *pgxpool.Pool) *UserHandler {
    repo := &PgUserRepository{db: db}
    service := NewUserService(repo)
    handler := NewUserHandler(service)
    return handler
}

 

This is Pure DI clean, explicit, and magic-free.

 

⭐ 3) DI Container in Node.js (Express, NestJS, Awilix)

 

In the Node.js ecosystem, DI containers are widely used:

  • Awilix (most popular for Express)
  • Tsyringe
  • InversifyJS
  • NestJS (built-in DI system)

 

🟧 Example: DI Using Awilix (Express)

 

Install packages

npm install awilix awilix-express

 

Create the container

import { createContainer, asClass } from "awilix";

const container = createContainer();

container.register({
  userRepository: asClass(UserRepository),
  userService: asClass(UserService),
  userController: asClass(UserController)
});

 

Use it in Express

import { scopePerRequest } from "awilix-express";
app.use(scopePerRequest(container));

 

Awilix injects dependencies automatically per request.

 

⭐ 4) Mocking Repositories & Services for Easy Testing

 

🟦 Go Mock Repository

type MockUserRepo struct {
    data map[string]User
}

func (m *MockUserRepo) FindByID(id string) (*User, error) {
    if u, ok := m.data[id]; ok {
        return &u, nil
    }
    return nil, errors.New("not found")
}

 

Unit Test

repo := &MockUserRepo{data: map[string]User{
    "1": {ID: "1", Name: "Ploy"},
}}

service := NewUserService(repo)
user, _ := service.GetUser("1")

 

✔ No DB required
✔ Fast and deterministic
✔ Perfect for unit testing

 

🟧 Node.js Mock Repository

const mockRepo = {
  findById: async (id) => ({ id, name: "Ploy" })
};

const service = new UserService(mockRepo);

 

The test becomes isolated and predictable.

 

⭐ 5) Designing a Correct Dependency Graph

 

Golden Rules:

Controller → Service → Repository → Database
  • Controllers must NOT touch the database directly
  • Services must NOT instantiate dependencies
  • Repositories must NOT contain business logic
  • No circular dependencies

 

Example Dependency Graph

HTTP Handler
     ↓
Service Layer
     ↓
Repository Layer
     ↓
Database

 

Large systems extend this pattern:

OrderAPI → OrderService → OrderRepo → PostgreSQL
                    ↑
              PaymentService → PaymentGateway
                    ↑
             InventoryService → InventoryRepo

 

DI keeps the entire graph clear and maintainable.

 

⭐ 6) Best Practices (Go + Node.js)

 

✔ Prefer constructor injection
✔ Use interfaces (Go) or abstractions (JS)
✔ Avoid global state
✔ Use DI container only when necessary
✔ Mock dependencies in all unit tests
✔ Keep config separate from domain logic
✔ Dependency flow must be one direction (no cycles)

 


 

📌 Summary

 

Dependency Injection is not for large corporations only. It is a fundamental skill every backend developer must master.

 

DI allows your system to be:

🚀 Easy to test
🧱 Low-coupled
🔄 Flexible to change
🔍 Easy to debug
📦 Scalable for real production workloads

 

Go → Constructor Injection = best practice

Node.js → Awilix / NestJS for powerful DI

 

A clean DI + dependency graph = a system truly ready for Production-scale growth.

 

🔵 Coming Next: EP.48 Logging & Monitoring for Production

 

You will learn:

  • Structured Logging (Zap, Zerolog, Pino)
  • Distributed Tracing (OpenTelemetry)
  • Metrics (Prometheus + Grafana)
  • Error Monitoring (Sentry)
  • Correlation IDs for microservices