MikroORM

MikroORM is an ORM that adopts a TypeScript-first approach. It implements Data Mapper pattern, Unit of Work, and Identity Map patterns, supporting MongoDB, MySQL, MariaDB, PostgreSQL, and SQLite while providing strict type safety.

ORMTypeScriptDatabaseData MapperMicro ORM

GitHub Overview

mikro-orm/mikro-orm

TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.

Stars8,427
Watchers49
Forks587
Created:March 15, 2018
Language:TypeScript
License:MIT License

Topics

databasedatamapperentitiesentityidentity-mapjavascriptlibsqlmongodbmysqlnodejsormpostgrepostgresqlsqlsql-serversqlitesqlite3typescripttypescript-ormunit-of-work

Star History

mikro-orm/mikro-orm Star History
Data as of: 8/13/2025, 01:43 AM

Library

MikroORM

Overview

MikroORM is an ORM that adopts a TypeScript-first approach. It implements Data Mapper pattern, Unit of Work, and Identity Map patterns, supporting MongoDB, MySQL, MariaDB, PostgreSQL, and SQLite while providing strict type safety.

Details

MikroORM applies proven architectural patterns from enterprise applications to modern TypeScript environments. Built based on patterns outlined in Martin Fowler's "Patterns of Enterprise Application Architecture," it's optimized for developing large-scale, complex applications.

Key Features

  • TypeScript-first: Complete type safety and IDE support
  • Data Mapper Pattern: Separation of domain logic and database access
  • Unit of Work: Efficient batch updates and transaction management
  • Identity Map: Memory-based object uniqueness guarantee
  • Relation Loading: Flexible control of Lazy/Eager loading

Pros and Cons

Pros

  • Excellent developer experience for TypeScript developers
  • Robustness through enterprise-grade architectural patterns
  • Optimal for modern web applications handling complex data relationships
  • Early bug detection through strict type safety
  • Active community and continuous development

Cons

  • Relatively high learning curve (especially for Active Record experienced developers)
  • Potential over-engineering for small projects
  • Configuration can become complex
  • Performance tuning requires understanding of ORM internals

Reference Pages

Code Examples

Installation and Basic Setup

npm install @mikro-orm/core @mikro-orm/cli
npm install @mikro-orm/postgresql # For PostgreSQL
npm install @mikro-orm/migrations # Migration features
// mikro-orm.config.ts
import { defineConfig } from '@mikro-orm/postgresql';

export default defineConfig({
  entities: ['./src/entities'],
  entitiesTs: ['./src/entities'],
  dbName: 'my_database',
  host: 'localhost',
  port: 5432,
  user: 'postgres',
  password: 'password',
  debug: true,
  migrations: {
    path: './src/migrations',
    pathTs: './src/migrations',
  },
});

Basic CRUD Operations (Model Definition, Create, Read, Update, Delete)

// entities/User.ts
import { Entity, PrimaryKey, Property, OneToMany, Collection } from '@mikro-orm/core';
import { Post } from './Post';

@Entity()
export class User {
  @PrimaryKey()
  id!: number;

  @Property()
  name!: string;

  @Property({ unique: true })
  email!: string;

  @Property()
  createdAt = new Date();

  @OneToMany(() => Post, post => post.author)
  posts = new Collection<Post>(this);
}

// entities/Post.ts
import { Entity, PrimaryKey, Property, ManyToOne } from '@mikro-orm/core';
import { User } from './User';

@Entity()
export class Post {
  @PrimaryKey()
  id!: number;

  @Property()
  title!: string;

  @Property({ type: 'text', nullable: true })
  content?: string;

  @ManyToOne(() => User)
  author!: User;

  @Property()
  createdAt = new Date();
}

// Basic CRUD operations
import { MikroORM } from '@mikro-orm/core';
import { User, Post } from './entities';

const orm = await MikroORM.init();
const em = orm.em.fork();

// Create
const user = new User();
user.name = 'John Doe';
user.email = '[email protected]';

await em.persistAndFlush(user);

// Read
const users = await em.find(User, {});
const userById = await em.findOne(User, { id: 1 });

// Update
const user = await em.findOne(User, { id: 1 });
if (user) {
  user.name = 'John Smith';
  await em.flush();
}

// Delete
const user = await em.findOne(User, { id: 1 });
if (user) {
  await em.removeAndFlush(user);
}

Advanced Queries and Relationships

// Complex queries
const usersWithRecentPosts = await em.find(User, {
  posts: {
    createdAt: { $gte: new Date('2025-01-01') }
  }
}, {
  populate: ['posts']
});

// Using QueryBuilder
const qb = em.createQueryBuilder(User, 'u');
const users = await qb
  .select(['u.*', 'p.title'])
  .leftJoin('u.posts', 'p')
  .where({ 'u.email': { $like: '%@company.com' } })
  .andWhere({ 'p.createdAt': { $gte: new Date('2025-01-01') } })
  .orderBy({ 'u.createdAt': 'DESC' })
  .limit(10)
  .getResult();

// Eager Loading
const usersWithPosts = await em.find(User, {}, {
  populate: ['posts']
});

// Lazy Loading
const user = await em.findOne(User, { id: 1 });
if (user) {
  await user.posts.init(); // Explicit initialization
  console.log(user.posts.getItems());
}

// Aggregation queries
const postCount = await em.count(Post, {
  author: { email: { $like: '%@company.com' } }
});

Migrations and Schema Management

# Generate migrations
npx mikro-orm migration:create

# Execute migrations
npx mikro-orm migration:up

# Update schema (development only)
npx mikro-orm schema:update --run
// Programmatic migrations
import { Migration } from '@mikro-orm/migrations';

export class Migration20250622000000 extends Migration {
  async up(): Promise<void> {
    this.addSql('create table "user" ("id" serial primary key, "name" varchar(255) not null, "email" varchar(255) not null);');
    this.addSql('alter table "user" add constraint "user_email_unique" unique ("email");');
  }

  async down(): Promise<void> {
    this.addSql('drop table if exists "user" cascade;');
  }
}

// Migration on application startup
import { MikroORM } from '@mikro-orm/core';

const orm = await MikroORM.init();
await orm.migrator.up();

Performance Optimization and Advanced Features

// Transactions
await em.transactional(async (em) => {
  const user = new User();
  user.name = 'Jane Doe';
  user.email = '[email protected]';
  em.persist(user);

  const post = new Post();
  post.title = 'First Post';
  post.content = 'My first post using MikroORM';
  post.author = user;
  em.persist(post);
});

// Batch operations and Unit of Work
const users = [
  { name: 'User 1', email: '[email protected]' },
  { name: 'User 2', email: '[email protected]' },
  { name: 'User 3', email: '[email protected]' },
];

users.forEach(userData => {
  const user = new User();
  Object.assign(user, userData);
  em.persist(user);
});

await em.flush(); // Batch save to database

// Caching
const user = await em.findOne(User, { id: 1 });
// Within same EntityManager, returns cached instance for same ID
const sameUser = await em.findOne(User, { id: 1 });
console.log(user === sameUser); // true

// Custom repository
@Repository(User)
export class UserRepository extends EntityRepository<User> {
  async findByEmail(email: string): Promise<User | null> {
    return this.findOne({ email });
  }

  async findActiveUsers(): Promise<User[]> {
    return this.find({
      posts: { $ne: null }
    }, {
      populate: ['posts']
    });
  }
}

Framework Integration and Practical Examples

// Express.js integration
import express from 'express';
import { MikroORM, RequestContext } from '@mikro-orm/core';

const app = express();
const orm = await MikroORM.init();

app.use((req, res, next) => {
  RequestContext.create(orm.em, next);
});

app.get('/users', async (req, res) => {
  const em = RequestContext.getEntityManager()!;
  const users = await em.find(User, {});
  res.json(users);
});

// NestJS integration
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';

@Module({
  imports: [
    MikroOrmModule.forRoot(),
    MikroOrmModule.forFeature([User, Post])
  ],
})
export class AppModule {}

// Service usage
import { Injectable } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/core';
import { InjectRepository } from '@mikro-orm/nestjs';
import { EntityRepository } from '@mikro-orm/postgresql';

@Injectable()
export class UserService {
  constructor(
    private readonly em: EntityManager,
    @InjectRepository(User)
    private readonly userRepository: EntityRepository<User>
  ) {}

  async createUser(userData: Partial<User>): Promise<User> {
    const user = new User();
    Object.assign(user, userData);
    await this.em.persistAndFlush(user);
    return user;
  }

  async findUsers(): Promise<User[]> {
    return this.userRepository.findAll();
  }
}