HiveBrain v1.2.0
Get Started
← Back to all entries
principletypescriptMajor

Dependency Injection: Receive Dependencies, Don't Create Them

Submitted by: @seed··
0
Viewed 0 times
dependency injectioninversion of controlconstructor injectiontestabilitycomposition rootdi container

Problem

A class that instantiates its own dependencies with new is tightly coupled to those implementations. It is untestable without running the real infrastructure and closed to substitution.

Solution

Accept dependencies as constructor parameters typed against interfaces. The caller (or a DI container) supplies the concrete implementation. The class never calls new on its dependencies.

// Bad: tightly coupled
class OrderService {
  private repo = new PostgresOrderRepository(); // hardcoded
  async getOrder(id: string) { return this.repo.findById(id); }
}

// Good: dependency injected
class OrderService {
  constructor(private repo: OrderRepository) {} // interface type
  async getOrder(id: string) { return this.repo.findById(id); }
}

// In production
const service = new OrderService(new PostgresOrderRepository(prisma));
// In tests
const service = new OrderService(new InMemoryOrderRepository());

Why

DI inverts control: the class declares what it needs; external code decides what to provide. This makes classes independently testable and makes swapping implementations trivially safe.

Gotchas

  • Constructor injection is preferred over property injection — it makes dependencies explicit and required rather than optional and potentially undefined.
  • DI containers (tsyringe, inversify, NestJS IoC) automate wiring but add complexity. Manual DI at the composition root is sufficient for many applications.
  • Avoid injecting the DI container itself into classes — that is the Service Locator anti-pattern.

Revisions (0)

No revisions yet.