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
Diagram source
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 & D3ORM Selection
The active ORM is configured via the DB_ORM environment variable:
# .env
DB_ORM=typeorm # Options: typeorm | mikro-ormWhen 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
The platform uses a custom Multi-ORM decorator system that generates metadata for both TypeORM and MikroORM from a single entity definition.
Entity Definition
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
| 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
Decorators are conditionally applied based on the active ORM:
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
BaseEntity
The root entity class providing common fields:
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
Adds tenant scoping:
export abstract class TenantBaseEntity extends BaseEntity {
@MultiORMManyToOne(() => Tenant, { nullable: false })
tenant: ITenant;
@MultiORMColumn({ relationId: true })
tenantId: string;
}TenantOrganizationBaseEntity
Adds organization scoping (most common base class):
export abstract class TenantOrganizationBaseEntity extends TenantBaseEntity {
@MultiORMManyToOne(() => Organization, { nullable: true })
organization?: IOrganization;
@MultiORMColumn({ relationId: true, nullable: true })
organizationId?: string;
}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
TypeORM Repositories
@Injectable()
export class EmployeeService extends TenantAwareCrudService<Employee> {
constructor(
@InjectRepository(Employee)
private readonly typeOrmEmployeeRepository: TypeOrmEmployeeRepository,
private readonly mikroOrmEmployeeRepository: MikroOrmEmployeeRepository,
) {
super(typeOrmEmployeeRepository, mikroOrmEmployeeRepository);
}
}MikroORM Repositories
MikroORM uses the Unit of Work pattern with an EntityManager:
@Injectable()
export class MikroOrmEmployeeRepository extends MikroOrmBaseEntityRepository<Employee> {
constructor(em: EntityManager) {
super(em, Employee);
}
}Knex Queries
For complex queries that bypass ORM overhead:
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
TypeORM Migrations
TypeORM migrations are located in packages/core/src/lib/database/migrations/:
# Generate a new migration
yarn typeorm migration:generate -n MigrationName
# Run migrations
yarn typeorm migration:run
# Revert last migration
yarn typeorm migration:revertMikroORM Migrations
MikroORM uses its own migration system:
# Create migration
yarn mikro-orm migration:create
# Run migrations
yarn mikro-orm migration:up
# Revert
yarn mikro-orm migration:downKnex Migrations
Knex provides raw SQL migrations for complex schema changes:
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 | TypeORM | MikroORM | Knex | Production Ready |
|---|---|---|---|---|
| PostgreSQL | β | β | β | β Recommended |
| MySQL | β | β | β | β |
| SQLite | β | β | β | β οΈ Dev/Demo only |
| better-sqlite3 | β | β | β | β οΈ Dev/Demo only |
| MariaDB | β | β | β | β |
Best Practices
DO
- β
Use
MultiORM*decorators for all entity definitions - β Inject both TypeORM and MikroORM repositories in services
- β
Use
TenantAwareCrudServicefor automatic tenant scoping - β Use Knex for complex aggregation queries
- β Test with both TypeORM and MikroORM in CI
DON'T
- β Import TypeORM/MikroORM decorators directly (use Multi-ORM wrappers)
- β Use
createQueryBuilderwithout tenant filtering (see Tenant Filtering) - β Mix ORM-specific patterns in service classes
- β Assume TypeORM-specific behavior (e.g., eager loading) works in MikroORM
Related Pages
- TypeORM Setup β TypeORM-specific configuration
- MikroORM Setup β MikroORM-specific configuration
- Knex Setup β Knex-specific configuration
- Tenant Filtering β tenant bypass documentation
- Multi-ORM Entities β entity definition patterns