MikroORM
MikroORMは、TypeScript-firstアプローチを採用したORMです。Data Mapperパターン、Unit of Work、Identity Mapパターンを実装し、MongoDB、MySQL、MariaDB、PostgreSQL、SQLiteをサポートしながら厳密な型安全性を提供します。
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
スター履歴
データ取得日時: 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();
}
}