# Dependency Injection in clean-code-javascript: A Complete Guide to the Dependency Inversion Principle

> Discover dependency injection in clean-code-javascript. Learn how to decouple modules and boost testability by applying the Dependency Inversion Principle effectively.

- Repository: [Ryan McDermott/clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript)
- Tags: deep-dive
- Published: 2026-02-27

---

**Dependency injection in clean-code-javascript is the practical implementation of the Dependency Inversion Principle, where high-level modules receive their dependencies from external sources rather than instantiating them internally, dramatically reducing coupling and improving testability.**

The *clean-code-javascript* repository by Ryan McDermott translates Robert C. Martin’s SOLID principles into JavaScript-specific best practices. Within the [`README.md`](https://github.com/ryanmcdermott/clean-code-javascript/blob/main/README.md) file’s *Dependency Inversion Principle* section, the guide presents dependency injection (DI) as the primary mechanism for ensuring that high-level modules remain independent of low-level implementation details.

## The Dependency Inversion Principle Connection

According to the source code analysis of [`README.md`](https://github.com/ryanmcdermott/clean-code-javascript/blob/main/README.md), the guide explicitly states that "you've seen an implementation of this principle in the form of Dependency Injection (DI)" when discussing how to properly structure JavaScript modules. The core tenet is that **abstractions should not depend upon details; details should depend upon abstractions**.

In JavaScript, where formal interfaces don't exist, this contract is enforced through **duck typing**—agreeing on method names like `requestItem` rather than requiring specific class inheritance.

## Bad vs. Good Implementation Patterns

The repository provides concrete before-and-after examples in the [`README.md`](https://github.com/ryanmcdermott/clean-code-javascript/blob/main/README.md) to illustrate the transformation from tightly-coupled code to dependency injection.

### The Anti-Pattern: Direct Instantiation

The **bad example** shows `InventoryTracker` directly creating its dependency inside the constructor:

```javascript
class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }
  requestItem(item) { /* … */ }
}

class InventoryTracker {
  constructor(items) {
    this.items = items;
    // ❌ Directly creates a concrete requester → hard‑to‑test, hard‑to‑swap
    this.requester = new InventoryRequester();
  }
  requestItems() {
    this.items.forEach(item => this.requester.requestItem(item));
  }
}

```

**Problems identified:**
- `InventoryTracker` is tightly coupled to `InventoryRequester`
- Replacing the requester (e.g., with a WebSocket implementation) requires modifying the `InventoryTracker` class
- Unit testing requires complex mocking of internal instantiation

### The Solution: Constructor Injection

The **good example** demonstrates passing the dependency from the outside:

```javascript
class InventoryTracker {
  // ✅ The requester is supplied from the outside
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }
  requestItems() {
    this.items.forEach(item => this.requester.requestItem(item));
  }
}

class InventoryRequesterV1 { /* HTTP implementation */ }
class InventoryRequesterV2 { /* WebSocket implementation */ }

// Client code decides which implementation to inject
const tracker = new InventoryTracker(
  ["apples", "bananas"],
  new InventoryRequesterV2()
);
tracker.requestItems();

```

## Practical Dependency Injection Patterns

Beyond the basic constructor pattern shown in [`README.md`](https://github.com/ryanmcdermott/clean-code-javascript/blob/main/README.md), you can implement these additional strategies in JavaScript projects.

### Function-Based Injection

For simpler modules, pass dependencies as function parameters:

```javascript
// logger.js – low‑level dependency
export const consoleLogger = message => console.log(message);

// service.js – high‑level module
export class Service {
  constructor(logger) {
    this.logger = logger;            // injected
  }
  doWork() {
    this.logger('Work started');
    // … business logic …
    this.logger('Work finished');
  }
}

// app.js – composition root
import { consoleLogger } from './logger.js';
import { Service } from './service.js';

const service = new Service(consoleLogger);
service.doWork();

```

### Swappable Data Stores

Use injection to switch between storage implementations without changing business logic:

```javascript
// storeMemory.js
export class MemoryStore {
  constructor() { this.db = new Map(); }
  save(key, value) { this.db.set(key, value); }
  get(key) { return this.db.get(key); }
}

// storeLocal.js
export class LocalStorageStore {
  save(key, value) { localStorage.setItem(key, JSON.stringify(value)); }
  get(key) { return JSON.parse(localStorage.getItem(key)); }
}

// repository.js – high‑level
export class UserRepository {
  constructor(store) { this.store = store; }   // injected
  addUser(user) { this.store.save(user.id, user); }
  findUser(id) { return this.store.get(id); }
}

// index.js – choose implementation
import { MemoryStore } from './storeMemory.js';
import { UserRepository } from './repository.js';

const repo = new UserRepository(new MemoryStore());
// In production you could inject new LocalStorageStore() instead.

```

## Benefits of Dependency Injection in JavaScript

The `clean-code-javascript` guide emphasizes that DI **reduces coupling between modules**, which directly translates to:

- **Testability:** You can inject mock objects during unit testing without modifying source code
- **Flexibility:** Swap implementations (HTTP vs. WebSocket, Memory vs. LocalStorage) without touching dependent classes
- **Clear contracts:** Dependencies are explicitly declared in constructors, making the code's requirements self-documenting
- **Refactoring safety:** Changes to low-level implementations don't cascade into high-level business logic

## Summary

- **Dependency injection in clean-code-javascript** implements the Dependency Inversion Principle by ensuring high-level modules receive dependencies externally rather than creating them internally.
- The [`README.md`](https://github.com/ryanmcdermott/clean-code-javascript/blob/main/README.md) file demonstrates this through the `InventoryTracker` example, contrasting tight coupling (direct instantiation) with loose coupling (constructor injection).
- **Constructor injection** is the preferred pattern, allowing you to swap implementations like `InventoryRequesterV1` (HTTP) with `InventoryRequesterV2` (WebSocket) without modifying the consumer.
- JavaScript achieves abstraction through **duck typing**—agreeing on method signatures like `requestItem` rather than requiring formal interfaces.
- The primary benefits include improved **testability** (mock injection), **flexibility** (runtime implementation swapping), and **reduced coupling** between modules.

## Frequently Asked Questions

### What is the difference between dependency injection and the Dependency Inversion Principle in clean-code-javascript?

The **Dependency Inversion Principle (DIP)** is the theoretical rule stating that high-level modules should not depend on low-level modules, but both should depend on abstractions. **Dependency injection (DI)** is the practical implementation technique used to achieve DIP in JavaScript code. According to the `clean-code-javascript` guide, DI is how you "pass the dependencies of a class or function from the outside" rather than letting the class create its own dependencies.

### How does clean-code-javascript suggest implementing dependency injection without interfaces?

Since JavaScript lacks a formal interface keyword, the guide relies on **duck typing** and **implicit contracts**. You define the expected behavior by documenting which methods the injected dependency must implement (such as `requestItem` in the `InventoryTracker` example). As long as the injected object has the required methods, it satisfies the contract. This allows you to inject mock objects during testing or swap between `InventoryRequesterV1` and `InventoryRequesterV2` without changing the consumer code.

### Why is constructor injection preferred over other forms of dependency injection in the guide?

The `clean-code-javascript` guide specifically highlights **constructor injection** because it makes dependencies explicit and immutable for the lifetime of the object. When dependencies are passed through the constructor (as shown in the `InventoryTracker` example), the class immediately declares what it needs to function, making the code self-documenting. This approach also prevents the class from changing its dependencies mid-execution, reducing side effects and making unit testing straightforward—you simply pass a mock object when instantiating the class in your tests.

### What are the main benefits of following the dependency injection patterns shown in clean-code-javascript?

The primary benefits emphasized in the repository include **reduced coupling** between high-level and low-level modules, which makes the codebase more modular and easier to refactor. **Testability** improves significantly because you can inject mock dependencies rather than fighting with concrete implementations that might make network calls or access databases. Finally, **flexibility** increases because you can swap implementations (such as changing from HTTP to WebSocket requesters) without modifying the dependent classes, adhering to the open/closed principle.