Node.js › Architecture & Design Patterns

What is Dependency Injection, and how is it typically implemented in Node.js given the language has no built-in DI container?

Intermediate

Answer

Dependency Injection means a component receives its dependencies (other objects or functions it needs) from the outside, typically via constructor or function parameters, rather than creating or looking them up itself -- this decouples the component from specific implementations and makes substituting mocks or fakes during testing straightforward. Node.js has no built-in DI container the way some other languages do, so DI is commonly implemented manually via constructor parameters and a manual composition step (sometimes called 'poor man's DI'), or via a small library like Awilix, InversifyJS, or tsyringe for larger applications.
// Manual DI via constructor parameters -- no framework needed
class OrderService {
  constructor(orderRepository, notificationService) {
    this.orderRepository = orderRepository;
    this.notificationService = notificationService;
  }
}

// Composition root: where all the wiring happens, typically once at startup
const orderService = new OrderService(new OrderRepository(db), new EmailNotificationService());
Real-world example A team testing their OrderService constructs it with a fake in-memory OrderRepository and a mock NotificationService in their unit tests, while the production composition root wires up the real database-backed repository and real email service -- all without changing a single line of OrderService itself.

Common follow-ups: At what team or project size does adopting a formal DI container like InversifyJS start paying off over manual wiring?;How does DI relate to and support the Dependency Inversion Principle from SOLID?