---
title: "Understanding the Dependency Inversion Principle with TypeScript and Databases"
description: "Learn how to implement the Dependency Inversion Principle in TypeScript to create flexible, maintainable database interactions."
slug: "understanding-the-dependency-inversion-principle-with-typescript-and-databases"
url: "https://furkanbaytekin.dev/blogs/understanding-the-dependency-inversion-principle-with-typescript-and-databases"
author: "Furkan Baytekin"
category: "Software"
tags: []
published: 2025-03-14T08:11:01Z
updated: 2025-03-14T08:11:01Z
readTime: 3
cover: "https://myblogcms.furkanbaytekin.dev/uploads/cover/understanding-the-dependency-inversion-principle-with-typescript-and-databases+1741527146356"
---

The Dependency Inversion Principle (DIP) is a key SOLID principle that helps us write flexible, maintainable code by decoupling high-level logic from low-level details. Instead of hardcoding dependencies, we rely on abstractions. Today, we’ll explore DIP with a practical example: a `UserService` that works with MongoDB, MySQL, and PostgreSQL, all in TypeScript.

## The Problem: Tight Coupling

Imagine a `UserService` that directly uses a MySQL database. If you later need to switch to MongoDB, you’d rewrite the service. That’s rigid and messy. DIP fixes this by making both the service and the database depend on an abstraction.

## Step 1: Define the Abstraction

We start with an interface that defines what our database should do—here, saving a user.

```typescript
interface UserRepository {
  saveUser(user: { id: string; name: string }): Promise<void>;
}
```

This is our contract. The `UserService` will depend on this, not a specific database.

## Step 2: Implement Concrete Repositories
Next, we create implementations for MongoDB, MySQL, and PostgreSQL, each adhering to `UserRepository`.

### MongoDB Repository
```typescript
import { MongoClient } from "mongodb";

class MongoUserRepository implements UserRepository {
  private client: MongoClient;

  constructor() {
    this.client = new MongoClient("mongodb://localhost:27017");
  }

  async saveUser(user: { id: string; name: string }): Promise<void> {
    const db = this.client.db("mydb");
    await db.collection("users").insertOne(user);
    console.log(`Saved ${user.name} to MongoDB`);
  }
}
```

### MySQL Repository
```typescript
import mysql from "mysql2/promise";

class MySQLUserRepository implements UserRepository {
  private connection: mysql.Connection;

  constructor() {
    this.connection = await mysql.createConnection({
      host: "localhost",
      user: "root",
      database: "mydb",
    });
  }

  async saveUser(user: { id: string; name: string }): Promise<void> {
    await this.connection.execute(
      "INSERT INTO users (id, name) VALUES (?, ?)",
      [user.id, user.name]
    );
    console.log(`Saved ${user.name} to MySQL`);
  }
}
```

### PostgreSQL Repository
```typescript
import { Pool } from "pg";

class PostgresUserRepository implements UserRepository {
  private pool: Pool;

  constructor() {
    this.pool = new Pool({
      user: "postgres",
      host: "localhost",
      database: "mydb",
      password: "password",
    });
  }

  async saveUser(user: { id: string; name: string }): Promise<void> {
    await this.pool.query(
      "INSERT INTO users (id, name) VALUES ($1, $2)",
      [user.id, user.name]
    );
    console.log(`Saved ${user.name} to PostgreSQL`);
  }
}
```

Each class implements `UserRepository`, but the details (SQL queries, MongoDB collections) are hidden behind the interface.

## Step 3: The High-Level Service
Now, the `UserService` depends only on the `UserRepository` abstraction, not the concrete databases.

```typescript
class UserService {
  private repository: UserRepository;

  constructor(repository: UserRepository) {
    this.repository = repository; // Dependency injected
  }

  async addUser(user: { id: string; name: string }) {
    await this.repository.saveUser(user);
  }
}
```

## Step 4: Putting It Together
We can now swap databases without touching `UserService`.

```typescript
async function main() {
  const user = { id: "1", name: "Alice" };

  // Use MongoDB
  const mongoRepo = new MongoUserRepository();
  const mongoService = new UserService(mongoRepo);
  await mongoService.addUser(user);

  // Switch to MySQL
  const mysqlRepo = new MySQLUserRepository();
  const mysqlService = new UserService(mysqlRepo);
  await mysqlService.addUser(user);

  // Switch to PostgreSQL
  const postgresRepo = new PostgresUserRepository();
  const postgresService = new UserService(postgresRepo);
  await postgresService.addUser(user);
}

main().catch(console.error);
```

## Why DIP Shines Here
- **Flexibility**: Need a new database? Just write a new repository class.
- **Testability**: Mock `UserRepository` for unit tests.
- **Clean Code**: `UserService` doesn’t care about database details—it’s blissfully ignorant.

## Wrapping Up
DIP turns dependencies upside down: instead of `UserService` dictating a specific database, both rely on the `UserRepository` interface. This small shift makes your TypeScript apps more modular and adaptable. Try it out next time you’re wiring up a service—your future self will thank you!

---

Album of the day:

<iframe style="border-radius:12px" src="https://open.spotify.com/embed/album/7crIwmJiCRxEJlfKRG43YZ?utm_source=generator" width="100%" height="152" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>
