mikro-orm для TypeScript: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

mikro-orm для TypeScript: полное руководство с примерами

1. Что это и зачем нужна?

Mikro-ORM — это современная объектно-реляционная библиотека (ORM) для TypeScript и JavaScript (Node.js), которая реализует паттерны Identity Map и Unit of Work. В отличие от классических ORM вроде TypeORM или Sequelize, Mikro-ORM вдохновлена Doctrine (PHP) и Hibernate (Java) и предлагает более предсказуемое управлением состоянием сущностей и их синхронизацией с базой данных.

Зачем она нужна? В типичных веб-приложениях разработчики часто сталкиваются с проблемой «грязных» данных, когда одна и та же запись загружается дважды в разных местах кода, что приводит к конфликтам при сохранении. Mikro-ORM решает эту проблему с помощью Identity Map: для каждого первичного ключа существует ровно один экземпляр сущности в рамках одного контекста (EntityManager). Это гарантирует, что вы всегда работаете с актуальной версией объекта, а все изменения отслеживаются автоматически.

Библиотека поддерживает множество СУБД (PostgreSQL, MySQL, SQLite, MongoDB, MariaDB) и предоставляет мощные инструменты: миграции, встроенную поддержку транзакций, каскадные операции, загрузку отношений (eager/lazy), встроенный валидатор схем и генератор сущностей из существующей базы данных. Mikro-ORM идеально подходит для сложных доменных моделей и микросервисной архитектуры, где важна согласованность данных.

2. Установка

Для установки Mikro-ORM и необходимого драйвера базы данных выполните команду через npm (или yarn/pnpm):

# Установка ядра библиотеки
npm install @mikro-orm/core

# Установка драйвера для вашей БД (выберите один):
npm install @mikro-orm/sqlite    # SQLite
npm install @mikro-orm/postgresql # PostgreSQL
npm install @mikro-orm/mysql      # MySQL / MariaDB
npm install @mikro-orm/mongodb    # MongoDB

# Дополнительно: CLI для миграций и генерации схем
npm install @mikro-orm/cli
npm install @mikro-orm/migrations  # для миграций

Также рекомендуется установить reflect-metadata для работы декораторов:

npm install reflect-metadata

Добавьте в tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2021",
    "module": "commonjs"
  }
}

3. Быстрый старт — минимальный рабочий пример

Создадим простое приложение на SQLite с одной сущностью User.

Шаг 1. Определяем сущность (entities/User.ts):

import { Entity, PrimaryKey, Property } from '@mikro-orm/core';

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

  @Property()
  name!: string;

  @Property()
  email!: string;

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

Шаг 2. Настраиваем Mikro-ORM (index.ts):

import 'reflect-metadata';
import { MikroORM } from '@mikro-orm/core';
import { SqliteDriver } from '@mikro-orm/sqlite';
import { User } from './entities/User';

async function main() {
  const orm = await MikroORM.init({
    entities: [User],
    dbName: 'test.db',
    driver: SqliteDriver,
    debug: true, // логи SQL-запросов
  });

  // Создаем схему (в dev-режиме)
  await orm.getSchemaGenerator().createSchema();

  // Получаем EntityManager
  const em = orm.em.fork();

  // Создаем пользователя
  const user = em.create(User, {
    name: 'Иван',
    email: 'ivan@example.com',
  });
  await em.flush(); // сохраняем в БД

  // Читаем пользователя
  const found = await em.findOne(User, { email: 'ivan@example.com' });
  console.log('Найден:', found);

  // Обновляем
  found!.name = 'Петр';
  await em.flush();

  // Удаляем
  await em.removeAndFlush(found!);

  await orm.close();
}

main().catch(console.error);

Запустите скрипт:

npx ts-node index.ts

Вы увидите SQL-запросы в консоли и созданный файл test.db.

4. Основные методы/функции/классы

4.1. MikroORM.init()

Сигнатура: static init(options: Options): Promise<MikroORM>

Что делает: Инициализирует ORM, создает пул соединений и загружает метаданные сущностей. Это главная точка входа.

Пример:

const orm = await MikroORM.init({
  entities: ['./dist/entities'], // путь к скомпилированным сущностям
  dbName: 'my_db',
  driver: PostgreSqlDriver,
  user: 'admin',
  password: 'secret',
});

4.2. EntityManager.flush()

Сигнатура: flush(): Promise<void>

Что делает: Синхронизирует все изменения (вставки, обновления, удаления) с базой данных в рамках одной транзакции. Является реализацией паттерна Unit of Work.

Пример:

const em = orm.em.fork();
const user = em.create(User, { name: 'Анна' });
user.name = 'Анна Смирнова';
await em.flush(); // INSERT + UPDATE выполнится одним запросом

4.3. EntityManager.find()

Сигнатура: find(entityName: EntityName<T>, where: FilterQuery<T>, options?: FindOptions<T>): Promise<T[]>

Что делает: Выполняет поиск сущностей по заданным критериям. Возвращает массив сущностей.

Пример:

const admins = await em.find(User, { role: 'admin' }, {
  populate: ['profile'], // загрузить связанные сущности
  limit: 10,
  orderBy: { createdAt: 'DESC' },
});

4.4. EntityManager.findOne()

Сигнатура: findOne(entityName: EntityName<T>, where: FilterQuery<T>, options?: FindOneOptions<T>): Promise<T | null>

Что делает: Ищет одну сущность по условию. Возвращает null, если ничего не найдено.

Пример:

const user = await em.findOne(User, { id: 1 });
if (user) {
  console.log(user.name);
}

4.5. EntityManager.create()

Сигнатура: create(entityName: EntityName<T>, data: Partial<T>): T

Что делает: Создает новый экземпляр сущности на основе переданных данных и помечает его для вставки при следующем flush(). Не выполняет запрос к БД.

Пример:

const user = em.create(User, {
  name: 'Ольга',
  email: 'olga@test.com',
});
// user теперь управляется EntityManager, но не сохранен

4.6. EntityManager.persist()

Сигнатура: persist(entity: AnyEntity | AnyEntity[]): void

Что делает: Помечает сущность (или массив сущностей) для сохранения. Отличие от create в том, что persist работает с уже существующими объектами.

Пример:

const user = new User();
user.name = 'Дмитрий';
em.persist(user); // теперь он будет сохранен при flush
await em.flush();

4.7. EntityManager.remove()

Сигнатура: remove(entity: AnyEntity): void

Что делает: Помечает сущность для удаления при следующем flush().

Пример:

const user = await em.findOne(User, 1);
em.remove(user!);
await em.flush(); // DELETE FROM user WHERE id = 1

4.8. EntityManager.fork()

Сигнатура: fork(options?: ForkOptions): EntityManager

Что делает: Создает новый дочерний EntityManager с собственным Identity Map. Полезно для обработки запросов в веб-приложениях, чтобы избежать проблем с кэшированием.

Пример:

// В каждом HTTP-запросе создаем новый контекст
app.get('/users', async (req, res) => {
  const em = orm.em.fork();
  const users = await em.find(User, {});
  res.json(users);
});

4.9. QueryBuilder — em.createQueryBuilder()

Сигнатура: createQueryBuilder(entityName: EntityName<T>, alias?: string): QueryBuilder<T>

Что делает: Создает построитель запросов для сложных SQL-запросов (JOIN, подзапросы, агрегации).

Пример:

const qb = em.createQueryBuilder(User, 'u');
const result = await qb
  .select('u.*')
  .leftJoinAndSelect('u.posts', 'p')
  .where({ 'u.active': true })
  .orderBy({ 'u.createdAt': 'DESC' })
  .limit(5)
  .execute();

4.10. EntityRepository — em.getRepository()

Сигнатура: getRepository(entityName: EntityName<T>): EntityRepository<T>

Что делает: Возвращает репозиторий для сущности, который предоставляет удобные методы для работы (find, findOne, persist и т.д.). Репозиторий можно использовать как сервис.

Пример:

const userRepo = em.getRepository(User);
const user = await userRepo.findOneOrFail({ email: 'test@test.com' });
await userRepo.nativeDelete({ active: false }); // DELETE напрямую

5. Реальный практический пример (100-200 строк кода)

Создадим мини-блог с сущностями User, Post и Comment, используя связи ManyToOne и OneToMany.

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;

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

entities/Post.ts:

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

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

  @Property()
  title!: string;

  @Property({ columnType: 'text' })
  content!: string;

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

  @OneToMany(() => Comment, comment => comment.post)
  comments = new Collection(this);

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

entities/Comment.ts:

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

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

  @Property()
  text!: string;

  @ManyToOne(() => Post)
  post!: Post;

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

app.ts — основной файл с логикой:

import 'reflect-metadata';
import { MikroORM, EntityManager } from '@mikro-orm/core';
import { SqliteDriver } from '@mikro-orm/sqlite';
import { User } from './entities/User';
import { Post } from './entities/Post';
import { Comment } from './entities/Comment';

async function blogExample() {
  const orm = await MikroORM.init({
    entities: [User, Post, Comment],
    dbName: 'blog.db',
    driver: SqliteDriver,
    debug: false,
  });

  // Создаем схему
  await orm.getSchemaGenerator().createSchema();

  const em = orm.em.fork();

  // 1. Создаем автора
  const author = em.create(User, {
    name: 'Мария',
    email: 'maria@blog.com',                    

Рекомендаций