MikroORM

MikroORMは、TypeScript-firstアプローチを採用したORMです。Data Mapperパターン、Unit of Work、Identity Mapパターンを実装し、MongoDB、MySQL、MariaDB、PostgreSQL、SQLiteをサポートしながら厳密な型安全性を提供します。

ORMTypeScriptデータベースData MapperマイクロORM

GitHub概要

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.

スター8,427
ウォッチ49
フォーク587
作成日:2018年3月15日
言語:TypeScript
ライセンス:MIT License

トピックス

databasedatamapperentitiesentityidentity-mapjavascriptlibsqlmongodbmysqlnodejsormpostgrepostgresqlsqlsql-serversqlitesqlite3typescripttypescript-ormunit-of-work

スター履歴

mikro-orm/mikro-orm Star History
データ取得日時: 2025/8/13 01:43

ライブラリ

MikroORM

概要

MikroORMは、TypeScript-firstアプローチを採用したORMです。Data Mapperパターン、Unit of Work、Identity Mapパターンを実装し、MongoDB、MySQL、MariaDB、PostgreSQL、SQLiteをサポートしながら厳密な型安全性を提供します。

詳細

MikroORMは、Enterprise向けアプリケーションで実績のあるアーキテクチャパターンを現代的なTypeScript環境に適用したORMです。Martin Fowlerの「Patterns of Enterprise Application Architecture」で提唱されたパターンを基に設計されており、大規模で複雑なアプリケーションの開発に最適化されています。

主な特徴

  • TypeScript-first: 完全な型安全性とIDEサポート
  • Data Mapperパターン: ドメインロジックとデータベースアクセスの分離
  • Unit of Work: 効率的なバッチ更新とトランザクション管理
  • Identity Map: メモリ内でのオブジェクト一意性保証
  • リレーションローディング: Lazy/Eager loadingの柔軟な制御

メリット・デメリット

メリット

  • TypeScript開発者にとって優れた開発体験
  • Enterprise級のアーキテクチャパターンによる堅牢性
  • 複雑なデータ関係を扱う現代的なWebアプリケーションに最適
  • 厳密な型安全性によるバグの早期発見
  • 活発なコミュニティと継続的な開発

デメリット

  • 学習コストが比較的高い(特にActive Record経験者)
  • 小規模プロジェクトではオーバーエンジニアリングの可能性
  • 設定が複雑になる場合がある
  • パフォーマンスチューニングにはORMの内部理解が必要

参考ページ

書き方の例

インストールと基本セットアップ

npm install @mikro-orm/core @mikro-orm/cli
npm install @mikro-orm/postgresql # PostgreSQLの場合
npm install @mikro-orm/migrations # マイグレーション機能
// 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',
  },
});

基本的なCRUD操作(モデル定義、作成、読み取り、更新、削除)

// 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();
}

// 基本的なCRUD操作
import { MikroORM } from '@mikro-orm/core';
import { User, Post } from './entities';

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

// 作成
const user = new User();
user.name = '田中太郎';
user.email = '[email protected]';

await em.persistAndFlush(user);

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

// 更新
const user = await em.findOne(User, { id: 1 });
if (user) {
  user.name = '田中次郎';
  await em.flush();
}

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

高度なクエリとリレーションシップ

// 複雑なクエリ
const usersWithRecentPosts = await em.find(User, {
  posts: {
    createdAt: { $gte: new Date('2025-01-01') }
  }
}, {
  populate: ['posts']
});

// 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(); // 明示的な初期化
  console.log(user.posts.getItems());
}

// 集約クエリ
const postCount = await em.count(Post, {
  author: { email: { $like: '%@company.com' } }
});

マイグレーションとスキーマ管理

# マイグレーション生成
npx mikro-orm migration:create

# マイグレーション実行
npx mikro-orm migration:up

# スキーマ更新(開発時のみ)
npx mikro-orm schema:update --run
// プログラマティックマイグレーション
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;');
  }
}

// アプリケーション起動時のマイグレーション
import { MikroORM } from '@mikro-orm/core';

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

パフォーマンス最適化と高度な機能

// トランザクション
await em.transactional(async (em) => {
  const user = new User();
  user.name = '山田花子';
  user.email = '[email protected]';
  em.persist(user);

  const post = new Post();
  post.title = '最初の投稿';
  post.content = 'MikroORMを使った投稿です';
  post.author = user;
  em.persist(post);
});

// バッチ操作とUnit of Work
const users = [
  { name: 'ユーザー1', email: '[email protected]' },
  { name: 'ユーザー2', email: '[email protected]' },
  { name: 'ユーザー3', email: '[email protected]' },
];

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

await em.flush(); // 一括でデータベースに保存

// キャッシング
const user = await em.findOne(User, { id: 1 });
// 同じEntityManagerでは、同じIDの場合はキャッシュされたインスタンスを返す
const sameUser = await em.findOne(User, { id: 1 });
console.log(user === sameUser); // true

// カスタムリポジトリ
@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']
    });
  }
}

フレームワーク統合と実用例

// Express.js統合
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統合
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';

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

// サービスでの使用
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();
  }
}