DEV Community

Cover image for Dependency Injection(DI) in JavaScript
Satish
Satish

Posted on

Dependency Injection(DI) in JavaScript

Dependency Injection (DI) in JavaScript is a design pattern where an object or function receives the objects it needs (dependencies) from an external source, rather than creating them internally. This approach promotes loose coupling, making code more modular, reusable, and significantly easier to unit test.

Here’s a super simple explanation of Dependency Injection (DI):

🚗 Imagine building a car:

  • The car needs an engine to run.
  • You could build the engine inside the car itself—but that makes it harder to change or fix later. 🧰 Instead, you use Dependency Injection:
  • You give the car an engine from outside.
  • Now, the car doesn’t care how the engine was made—it just uses it.

💡 In programming terms:

  • A class (like Car) needs other objects (like Engine) to work. These are called dependencies.
  • With DI, you pass those dependencies into the class, instead of letting the class create them itself.

✅ Why it’s useful:

  • Makes your code flexible and easy to test.
  • You can swap parts (like using a fake engine for testing).
  • Keeps your code clean and modular.

Here’s a simple JavaScript example to show how Dependency Injection (DI) works:

🧱 Without Dependency Injection (tight coupling):

class Engine {
  start() {
    console.log("Engine started");
  }
}

class Car {
  constructor() {
    this.engine = new Engine(); // Car creates its own engine
  }

  drive() {
    this.engine.start();
    console.log("Car is driving");
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Problem: Car is tightly connected to Engine. You can’t easily swap the engine or test the car with a fake one.

🔧 With Dependency Injection (loose coupling):

class Engine {
  start() {
    console.log("Engine started");
  }
}

class Car {
  constructor(engine) {
    this.engine = engine; // Engine is injected from outside
  }

  drive() {
    this.engine.start();
    console.log("Car is driving");
  }
}

// Injecting the dependency
const myEngine = new Engine();
const myCar = new Car(myEngine);
myCar.drive();
Enter fullscreen mode Exit fullscreen mode
  • Benefit: You can now pass in any engine—even a mock one for testing

✅ Summary:

  • DI means giving a class what it needs, instead of letting it build those things itself.
  • It makes your code flexible, testable, and easier to maintain.

Let’s look at Dependency Injection (DI) in TypeScript/NestJS, since that’s where it really shines.

🔧 TypeScript Example (without a framework:

class Engine {
  start() {
    console.log("Engine started");
  }
}

class Car {
  private engine: Engine;

  // Dependency is injected through the constructor
  constructor(engine: Engine) {
    this.engine = engine;
  }

  drive() {
    this.engine.start();
    console.log("Car is driving");
  }
}

// Injecting dependency
const engine = new Engine();
const car = new Car(engine);
car.drive();
Enter fullscreen mode Exit fullscreen mode

👉 Here, the Car doesn’t create its own Engine. Instead, we pass it in. That’s DI

🚀 NestJS Example (with DI container):
NestJS has a built-in dependency injection system using decorators.

import { Injectable } from '@nestjs/common';

@Injectable()
class Engine {
  start() {
    console.log("Engine started");
  }
}

@Injectable()
class Car {
  constructor(private engine: Engine) {} // NestJS injects Engine automatically

  drive() {
    this.engine.start();
    console.log("Car is driving");
  }
}
Enter fullscreen mode Exit fullscreen mode
  • @Injectable() tells NestJS that this class can be managed by its DI container.
  • When you ask for a Car, NestJS automatically provides an Engine instance.
  • You don’t need to manually create objects—NestJS handles it.

✅ Why DI matters in frameworks:

  • Loose coupling → classes don’t depend on each other directly.
  • Easy testing → you can inject fake/mock services.
  • Scalability → NestJS automatically wires dependencies, so large apps stay organized.

Top comments (0)