# How to Add New Repository Implementations for Different Data Sources in the castrozan/tcc Monorepo

> Learn how to add new repository implementations for different data sources in the castrozan/tcc monorepo. Implement domain interfaces and update index.ts to export your new classes.

- Repository: [Lucas Zanoni⠀⠀⠀⠀⠀ ⠀╱|、 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ (˚ˎ 。7 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |、˜〵 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ じしˍ,)ノ/tcc](https://github.com/castrozan/tcc)
- Tags: how-to-guide
- Published: 2026-02-27

---

**You can add new repository implementations for different data sources by implementing the domain interface contract in the infrastructure layer and updating the corresponding [`index.ts`](https://github.com/castrozan/tcc/blob/main/index.ts) file to export your new concrete class.**

The castrozan/tcc repository demonstrates a clean Domain-Driven Design architecture that keeps domain logic completely agnostic of data persistence mechanisms. This guide explains how to add new repository implementations for different data sources—such as PostgreSQL, MySQL, or MongoDB—without touching the application or presentation layers.

## Architecture Overview

The dummy-apps in this monorepo follow a strict three-layer Repository pattern that separates domain contracts from infrastructure details.

**Domain Layer**: Declares interface contracts that define required operations. In [`src/domain/interfaces/professionals/IProfessionalRepository.ts`](https://github.com/castrozan/tcc/blob/main/src/domain/interfaces/professionals/IProfessionalRepository.ts), the `IProfessionalRepository` interface specifies five core methods—`create`, `findById`, `findAll`, `update`, and `delete`—for the `Professional` aggregate.

**Infrastructure Layer**: Provides concrete implementations using specific data-access technologies. The current SQLite implementation in [`src/infrastructure/database/repositories/professionals/SQLiteProfessionalRepository.ts`](https://github.com/castrozan/tcc/blob/main/src/infrastructure/database/repositories/professionals/SQLiteProfessionalRepository.ts) uses **better-sqlite3** to fulfill the interface contract.

**Export Layer**: A thin wiring file that instantiates the concrete repository and exports it as the default. The [`src/infrastructure/database/repositories/professionals/index.ts`](https://github.com/castrozan/tcc/blob/main/src/infrastructure/database/repositories/professionals/index.ts) file creates a singleton instance of `SQLiteProfessionalRepository` and exposes it to the application layer.

Because application code depends only on the `IProfessionalRepository` interface, you can swap SQLite for any other data source by providing a new class implementation and updating the export file.

## Step-by-Step Guide to Add New Repository Implementations for Different Data Sources

Follow these eight steps to integrate a new data source into the castrozan/tcc architecture.

### 1. Identify the Domain Contract

Locate the repository interface that defines the contract for your entity. These interfaces reside in `src/domain/interfaces/<entity>/I<Entity>Repository.ts`. For example, `IProfessionalRepository` in [`professionals-dummy-app/src/domain/interfaces/professionals/IProfessionalRepository.ts`](https://github.com/castrozan/tcc/blob/main/professionals-dummy-app/src/domain/interfaces/professionals/IProfessionalRepository.ts) declares the method signatures your implementation must satisfy.

### 2. Create the Implementation File

Add a new TypeScript file in `src/infrastructure/database/repositories/<entity>/<YourDataSource>Repository.ts`. This class must explicitly implement the domain interface using the `implements` keyword. Choose your preferred data-access library—such as Prisma, TypeORM, Sequelize, or native drivers like `pg` for PostgreSQL.

### 3. Implement All Interface Methods

Translate each contract method to the specific query language of your data source. Maintain identical method signatures including return types (typically `Promise<Professional>` or `Promise<Professional[]>`) and parameter structures. The interface mandates async behavior for all data operations.

### 4. Add Connection Helpers (Optional)

If your data source requires a client instance or connection pool, create a helper file similar to [`src/infrastructure/database/sqlite/sqlite-client.ts`](https://github.com/castrozan/tcc/blob/main/src/infrastructure/database/sqlite/sqlite-client.ts). Place this in `src/infrastructure/database/<datasource>/<client>.ts` and export a singleton or factory function for your repository to consume.

### 5. Wire the Implementation

Modify the [`index.ts`](https://github.com/castrozan/tcc/blob/main/index.ts) file in `src/infrastructure/database/repositories/<entity>/index.ts` to instantiate your new class instead of the default SQLite implementation. Change the import statement and update the default export variable to use your new repository class.

### 6. Update Imports and Types

Ensure any custom ORM models or database-specific types used by your implementation are properly exported from a central location, typically `src/infrastructure/database/models`. This maintains clean dependency management across the infrastructure layer.

### 7. Validate with Existing Tests

Run the existing unit test suite using `npm test` or the repository's specified test command. The tests exercise repositories through the interface abstraction; they should pass unchanged if your implementation respects the contract signatures and behavior.

### 8. Document Environment Requirements

Add documentation in [`README.md`](https://github.com/castrozan/tcc/blob/main/README.md) or a dedicated `docs/` file describing the new data source, required environment variables (connection strings, credentials), and any specific setup instructions for developers.

## Complete Implementation Example: PostgreSQL Repository

The following example demonstrates a complete PostgreSQL implementation using the native `pg` library. This replaces the SQLite repository for the Professional entity.

First, create the repository implementation:

```ts
// src/infrastructure/database/repositories/professionals/PostgresProfessionalRepository.ts
import { IProfessionalRepository } from 'domain/interfaces/professionals/IProfessionalRepository';
import { Professional } from 'domain/entities/professionals/Professional';
import { getPgPool } from '../../postgres/postgres-client';

export class PostgresProfessionalRepository implements IProfessionalRepository {
  async create(dto: Partial<Professional>): Promise<Professional> {
    const pool = getPgPool();
    const result = await pool.query(
      `INSERT INTO professional (name, role, bio, "imageUrl", "createdAt", hierarchy)
       VALUES ($1,$2,$3,$4,$5,$6) RETURNING *`,
      [dto.name, dto.role, dto.bio, dto.imageUrl, new Date(), dto.hierarchy]
    );
    const row = result.rows[0];
    return new Professional(
      row.id,
      row.name,
      row.role,
      row.bio,
      row.imageUrl,
      row.createdAt,
      row.hierarchy
    );
  }

  async findById(id: number): Promise<Professional | null> {
    const pool = getPgPool();
    const { rows } = await pool.query('SELECT * FROM professional WHERE id = $1', [id]);
    if (!rows.length) return null;
    const r = rows[0];
    return new Professional(r.id, r.name, r.role, r.bio, r.imageUrl, r.createdAt, r.hierarchy);
  }

  async findAll(): Promise<Professional[]> {
    const pool = getPgPool();
    const { rows } = await pool.query('SELECT * FROM professional');
    return rows.map(
      r => new Professional(r.id, r.name, r.role, r.bio, r.imageUrl, r.createdAt, r.hierarchy)
    );
  }

  async update(dto: Partial<Professional>): Promise<Professional> {
    if (!dto.id) throw new Error('Professional ID required');
    const pool = getPgPool();
    await pool.query(
      `UPDATE professional SET name=$1, role=$2, bio=$3, "imageUrl"=$4, hierarchy=$5 WHERE id=$6`,
      [dto.name, dto.role, dto.bio, dto.imageUrl, dto.hierarchy, dto.id]
    );
    return this.findById(dto.id) as Promise<Professional>;
  }

  async delete(id: number): Promise<void> {
    const pool = getPgPool();
    await pool.query('DELETE FROM professional WHERE id = $1', [id]);
  }
}

```

Then, wire the implementation in the index file:

```ts
// src/infrastructure/database/repositories/professionals/index.ts
import { IProfessionalRepository } from 'domain/interfaces/professionals/IProfessionalRepository';
import { PostgresProfessionalRepository } from './PostgresProfessionalRepository';
// Switch to SQLite by importing ./SQLiteProfessionalRepository instead

const ProfessionalRepository: IProfessionalRepository = new PostgresProfessionalRepository();

export default ProfessionalRepository;

```

## Key Source Files and Reference Implementations

Understanding the existing codebase helps ensure your implementation follows established patterns:

- **[`src/domain/interfaces/professionals/IProfessionalRepository.ts`](https://github.com/castrozan/tcc/blob/main/src/domain/interfaces/professionals/IProfessionalRepository.ts)**: Defines the contract with `create`, `findById`, `findAll`, `update`, and `delete` methods.
- **[`src/infrastructure/database/repositories/professionals/SQLiteProfessionalRepository.ts`](https://github.com/castrozan/tcc/blob/main/src/infrastructure/database/repositories/professionals/SQLiteProfessionalRepository.ts)**: Reference implementation using **better-sqlite3**.
- **[`src/infrastructure/database/repositories/professionals/index.ts`](https://github.com/castrozan/tcc/blob/main/src/infrastructure/database/repositories/professionals/index.ts)**: Wiring file that exports the concrete repository instance.
- **[`src/infrastructure/database/sqlite/sqlite-client.ts`](https://github.com/castrozan/tcc/blob/main/src/infrastructure/database/sqlite/sqlite-client.ts)**: Connection helper pattern for managing database clients.
- **[`src/domain/interfaces/equipments/IEquipmentRepository.ts`](https://github.com/castrozan/tcc/blob/main/src/domain/interfaces/equipments/IEquipmentRepository.ts)**: Secondary example showing the same pattern applied to the Equipment domain.
- **[`src/infrastructure/database/repositories/equipments/SQLiteEquipmentRepository.ts`](https://github.com/castrozan/tcc/blob/main/src/infrastructure/database/repositories/equipments/SQLiteEquipmentRepository.ts)**: SQLite implementation for the Equipment entity.

## Summary

- **Domain-Driven Design separation**: The castrozan/tcc architecture isolates data persistence in the infrastructure layer through interface contracts defined in files like [`IProfessionalRepository.ts`](https://github.com/castrozan/tcc/blob/main/IProfessionalRepository.ts).
- **Interface-based swapping**: New repository implementations for different data sources require only implementing the domain interface and updating the [`index.ts`](https://github.com/castrozan/tcc/blob/main/index.ts) export to instantiate your new class.
- **Zero application changes**: Because services depend on interfaces rather than concrete classes, switching from SQLite to PostgreSQL or MongoDB requires no modifications to business logic or controllers.
- **Test compatibility**: Existing unit tests validate implementations through the interface abstraction, ensuring contract compliance without test modifications.
- **Connection management**: Follow the [`sqlite-client.ts`](https://github.com/castrozan/tcc/blob/main/sqlite-client.ts) pattern to encapsulate data-source specific connection logic in dedicated client helpers within `src/infrastructure/database/<datasource>/`.

## Frequently Asked Questions

### Do I need to modify application layer code when adding a new repository implementation?

No. The application and presentation layers depend only on the domain interfaces such as `IProfessionalRepository`. By updating only the infrastructure implementation and the [`index.ts`](https://github.com/castrozan/tcc/blob/main/index.ts) wiring file, you swap data sources without touching controllers, services, or domain logic.

### Can I use ORMs like Prisma or TypeORM instead of raw SQL queries?

Yes. The repository pattern accepts any implementation that satisfies the interface contract. You can use Prisma Client, TypeORM repositories, or Sequelize models inside your concrete class methods, provided you map the ORM results to the domain entities expected by the interface return types.

### How do I ensure my new repository implementation works correctly?

Run the existing test suite with `npm test`. The unit tests exercise repositories through the interface abstraction, validating that `create`, `findById`, `findAll`, `update`, and `delete` behave according to the contract. If your implementation respects the method signatures and return types, all tests should pass without modification.

### What is the purpose of the [`index.ts`](https://github.com/castrozan/tcc/blob/main/index.ts) file in the repository folder?

The [`index.ts`](https://github.com/castrozan/tcc/blob/main/index.ts) file acts as a wiring layer that instantiates the concrete repository class and exports it as the default implementation. This pattern allows you to switch between different data sources—such as `SQLiteProfessionalRepository` and `PostgresProfessionalRepository`—by changing a single import and instantiation line, keeping the rest of the codebase agnostic to the specific database technology.