# Multi-ORM Architecture {#multi-orm-architecture}


Ever Gauzy uniquely supports **multiple ORMs simultaneously** — TypeORM, MikroORM, and Knex — allowing developers to choose the best tool for each use case while sharing the same entity definitions and database.


## Architecture Overview {#architecture-overview}






```mermaid
graph TB
    A["Application Code<br/>(Services, Controllers, Handlers)"]
    subgraph ORM["ORM Layer"]
        B1["TypeORM Repository"]
        B2["MikroORM Repository"]
        B3["Knex Query Builder"]
    end
    C["Multi-ORM Decorator Layer<br/>(Shared entity definitions & metadata)"]
    subgraph DB["Databases"]
        D1["PostgreSQL"]
        D2["MySQL"]
        D3["SQLite"]
    end
    A --> B1 & B2 & B3
    B1 & B2 & B3 --> C
    C --> D1 & D2 & D3
```






## ORM Selection {#orm-selection}


The active ORM is configured via the `DB_ORM` environment variable:

```bash
# .env
DB_ORM=typeorm       # Options: typeorm | mikro-orm
```


### When to Use Each ORM {#when-to-use-each-orm}


| ORM          | Best For                                                | Characteristics                                            |
| ------------ | ------------------------------------------------------- | ---------------------------------------------------------- |
| **TypeORM**  | General CRUD, complex relations, migrations             | Active Record + Data Mapper patterns, eager loading        |
| **MikroORM** | Strict data integrity, unit of work                     | Identity map, automatic change tracking, stricter metadata |
| **Knex**     | Raw SQL, complex aggregations, high-performance queries | Direct SQL control, query builder, raw performance         |


## Multi-ORM Decorators {#multi-orm-decorators}


The platform uses a custom **Multi-ORM decorator** system that generates metadata for both TypeORM and MikroORM from a single entity definition.


### Entity Definition {#entity-definition}


```typescript
import { MultiORMEntity, MultiORMColumn, MultiORMManyToOne } from "@gauzy/core";

@MultiORMEntity("employee")
export class Employee extends TenantOrganizationBaseEntity {
  @MultiORMColumn()
  firstName: string;

  @MultiORMColumn()
  lastName: string;

  @MultiORMColumn({ nullable: true })
  startedWorkOn?: Date;

  @MultiORMManyToOne(() => User, {
    nullable: false,
    onDelete: "CASCADE",
  })
  user: IUser;

  @MultiORMColumn({ relationId: true })
  userId: string;
}
```


### Decorator Mapping {#decorator-mapping}


| Multi-ORM Decorator     | TypeORM Equivalent | MikroORM Equivalent |
| ----------------------- | ------------------ | ------------------- |
| `@MultiORMEntity()`     | `@Entity()`        | `@Entity()`         |
| `@MultiORMColumn()`     | `@Column()`        | `@Property()`       |
| `@MultiORMManyToOne()`  | `@ManyToOne()`     | `@ManyToOne()`      |
| `@MultiORMOneToMany()`  | `@OneToMany()`     | `@OneToMany()`      |
| `@MultiORMManyToMany()` | `@ManyToMany()`    | `@ManyToMany()`     |
| `@MultiORMOneToOne()`   | `@OneToOne()`      | `@OneToOne()`       |


### Conditional Decorator Application {#conditional-decorator-application}


Decorators are conditionally applied based on the active ORM:

```typescript
export function MultiORMColumn(options?: ColumnOptions): PropertyDecorator {
  return (target, propertyKey) => {
    if (getDBORM() === "typeorm") {
      Column(options)(target, propertyKey);
    }
    if (getDBORM() === "mikro-orm") {
      Property(mapToMikroORMOptions(options))(target, propertyKey);
    }
  };
}
```

This prevents metadata conflicts when only one ORM is active at runtime.


## Base Entity Classes {#base-entity-classes}



### `BaseEntity` {#baseentity}


The root entity class providing common fields:

```typescript
export abstract class BaseEntity {
  @MultiORMColumn({ primary: true })
  id: string;

  @MultiORMColumn({ createDate: true })
  createdAt: Date;

  @MultiORMColumn({ updateDate: true })
  updatedAt: Date;

  @MultiORMColumn({ nullable: true })
  isActive: boolean;

  @MultiORMColumn({ nullable: true })
  isArchived: boolean;
}
```


### `TenantBaseEntity` {#tenantbaseentity}


Adds tenant scoping:

```typescript
export abstract class TenantBaseEntity extends BaseEntity {
  @MultiORMManyToOne(() => Tenant, { nullable: false })
  tenant: ITenant;

  @MultiORMColumn({ relationId: true })
  tenantId: string;
}
```


### `TenantOrganizationBaseEntity` {#tenantorganizationbaseentity}


Adds organization scoping (most common base class):

```typescript
export abstract class TenantOrganizationBaseEntity extends TenantBaseEntity {
  @MultiORMManyToOne(() => Organization, { nullable: true })
  organization?: IOrganization;

  @MultiORMColumn({ relationId: true, nullable: true })
  organizationId?: string;
}
```


### Entity Hierarchy {#entity-hierarchy}


```
BaseEntity
├── TenantBaseEntity
│   ├── User
│   ├── Role
│   ├── Tenant (self-referencing)
│   └── TenantOrganizationBaseEntity
│       ├── Employee
│       ├── OrganizationProject
│       ├── Task
│       ├── TimeLog
│       ├── Invoice
│       ├── Expense
│       ├── Contact
│       └── ... (most entities)
└── Model (non-DB entities)
```


## Repository Layer {#repository-layer}



### TypeORM Repositories {#typeorm-repositories}


```typescript
@Injectable()
export class EmployeeService extends TenantAwareCrudService<Employee> {
  constructor(
    @InjectRepository(Employee)
    private readonly typeOrmEmployeeRepository: TypeOrmEmployeeRepository,
    private readonly mikroOrmEmployeeRepository: MikroOrmEmployeeRepository,
  ) {
    super(typeOrmEmployeeRepository, mikroOrmEmployeeRepository);
  }
}
```


### MikroORM Repositories {#mikroorm-repositories}


MikroORM uses the Unit of Work pattern with an EntityManager:

```typescript
@Injectable()
export class MikroOrmEmployeeRepository extends MikroOrmBaseEntityRepository<Employee> {
  constructor(em: EntityManager) {
    super(em, Employee);
  }
}
```


### Knex Queries {#knex-queries}


For complex queries that bypass ORM overhead:

```typescript
import { Knex } from "knex";

@Injectable()
export class ReportService {
  constructor(@InjectConnection() private readonly knex: Knex) {}

  async getTimeSummary(tenantId: string, options: ITimeLogFilters) {
    return this.knex("time_log")
      .select("employee_id")
      .sum("duration as totalDuration")
      .where("tenant_id", tenantId)
      .groupBy("employee_id");
  }
}
```


## Migrations {#migrations}



### TypeORM Migrations {#typeorm-migrations}


TypeORM migrations are located in `packages/core/src/lib/database/migrations/`:

```bash
# Generate a new migration
yarn typeorm migration:generate -n MigrationName

# Run migrations
yarn typeorm migration:run

# Revert last migration
yarn typeorm migration:revert
```


### MikroORM Migrations {#mikroorm-migrations}


MikroORM uses its own migration system:

```bash
# Create migration
yarn mikro-orm migration:create

# Run migrations
yarn mikro-orm migration:up

# Revert
yarn mikro-orm migration:down
```


### Knex Migrations {#knex-migrations}


Knex provides raw SQL migrations for complex schema changes:

```typescript
export async function up(knex: Knex): Promise<void> {
  await knex.schema.alterTable("employee", (table) => {
    table.string("new_field").nullable();
  });
}

export async function down(knex: Knex): Promise<void> {
  await knex.schema.alterTable("employee", (table) => {
    table.dropColumn("new_field");
  });
}
```


## Database Support Matrix {#database-support-matrix}


| Database           | TypeORM | MikroORM | Knex | Production Ready |
| ------------------ | ------- | -------- | ---- | ---------------- |
| **PostgreSQL**     | ✅      | ✅       | ✅   | ✅ Recommended   |
| **MySQL**          | ✅      | ✅       | ✅   | ✅               |
| **SQLite**         | ✅      | ✅       | ✅   | ⚠️ Dev/Demo only |
| **better-sqlite3** | ✅      | ✅       | ✅   | ⚠️ Dev/Demo only |
| **MariaDB**        | ✅      | ✅       | ✅   | ✅               |


## Best Practices {#best-practices}



### DO {#do}


- ✅ Use `MultiORM*` decorators for all entity definitions
- ✅ Inject both TypeORM and MikroORM repositories in services
- ✅ Use `TenantAwareCrudService` for automatic tenant scoping
- ✅ Use Knex for complex aggregation queries
- ✅ Test with both TypeORM and MikroORM in CI


### DON'T {#dont}


- ❌ Import TypeORM/MikroORM decorators directly (use Multi-ORM wrappers)
- ❌ Use `createQueryBuilder` without tenant filtering (see [Tenant Filtering](https://sourcey-gauzy.pages.dev/database/tenant-filtering/index.md))
- ❌ Mix ORM-specific patterns in service classes
- ❌ Assume TypeORM-specific behavior (e.g., eager loading) works in MikroORM


## Related Pages {#related-pages}


- [TypeORM Setup](https://sourcey-gauzy.pages.dev/database/typeorm/index.md) — TypeORM-specific configuration
- [MikroORM Setup](https://sourcey-gauzy.pages.dev/database/mikroorm/index.md) — MikroORM-specific configuration
- [Knex Setup](https://sourcey-gauzy.pages.dev/database/knex/index.md) — Knex-specific configuration
- [Tenant Filtering](https://sourcey-gauzy.pages.dev/database/tenant-filtering/index.md) — tenant bypass documentation
- [Multi-ORM Entities](https://sourcey-gauzy.pages.dev/database/multi-orm-entities/index.md) — entity definition patterns
