Что такое TypeORM?

Ответ

TypeORM — это Object-Relational Mapper (ORM) для TypeScript и JavaScript, который работает в среде Node.js. Он позволяет взаимодействовать с реляционными базами данных (такими как PostgreSQL, MySQL, MariaDB, SQLite, Microsoft SQL Server) и даже с MongoDB, используя объектно-ориентированный подход: вы работаете с классами и объектами (сущностями), а TypeORM транслирует это в SQL-запросы.

Ключевые концепции и особенности:

  • Сущности (Entities): Классы, помеченные декоратором @Entity(), которые отображаются на таблицы в БД.

    import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
    
    @Entity('users') // Имя таблицы в БД
    export class User {
      @PrimaryGeneratedColumn()
      id: number;
    
      @Column({ type: 'varchar', length: 100 })
      name: string;
    
      @Column({ unique: true })
      email: string;
    }
  • Два паттерна работы:

    1. Active Record: Сущность наследует от базового класса BaseEntity и получает методы для работы с собой (save, find, remove).
      const user = new User();
      user.name = 'Alice';
      await user.save(); // Метод доступен, если User extends BaseEntity
    2. Data Mapper: Используются отдельные классы-репозитории (Repository), которые инкапсулируют всю логику работы с сущностью. Это считается более гибким и чистым подходом.
      // В сервисе
      const userRepository = dataSource.getRepository(User);
      const newUser = userRepository.create({ name: 'Bob', email: 'bob@mail.com' });
      await userRepository.save(newUser);
  • Связи (Relations): Легко определяются через декораторы (@OneToMany, @ManyToOne, @ManyToMany, @OneToOne).

  • Миграции (Migrations): Позволяют управлять изменениями схемы базы данных в контролируемом виде.

  • Query Builder: Мощный конструктор запросов для сложных операций.

Пример использования Data Mapper в приложении на Express + TypeORM:

// user.service.ts
import { AppDataSource } from './data-source'; // Настроенный DataSource
import { User } from '../entity/User';

export class UserService {
  private userRepository = AppDataSource.getRepository(User);

  async createUser(userData: Partial<User>): Promise<User> {
    const user = this.userRepository.create(userData);
    return await this.userRepository.save(user);
  }

  async findUserByEmail(email: string): Promise<User | null> {
    return await this.userRepository.findOne({ where: { email } });
  }
}

Ответ 18+ 🔞

Блин, слушай, вот есть такая штука — TypeORM. Ну, это такой Object-Relational Mapper, если по-умному, а по-простому — инструмент, чтобы не писать SQL вручную, а работать с базой данных как с обычными классами и объектами на TypeScript или JavaScript. Под капотом он сам эту всю хуйню в SQL-запросы переводит. Поддерживает он, ядрёна вошь, почти всё: PostgreSQL, MySQL, SQLite, Microsoft SQL Server и даже MongoDB.

Основные фишки, на которых всё держится:

  • Сущности (Entities): Это просто классы, которые ты помечаешь декоратором @Entity(). Каждый такой класс — будущая таблица в базе. Внутри описываешь колонки — и всё, магия начинается.

    import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
    
    @Entity('users') // Вот так таблица в базе и назовётся
    export class User {
      @PrimaryGeneratedColumn() // Автоинкрементный ID, ёпта
      id: number;
    
      @Column({ type: 'varchar', length: 100 }) // Обычная строка
      name: string;
    
      @Column({ unique: true }) // С уникальным ключом, чтобы почты не повторялись
      email: string;
    }

    Создал класс — и уже почти таблица готова. Удивление пиздец, как просто.

  • А работать с этим можно двумя способами, и тут уже выбор за тобой:

    1. Active Record: Твоя сущность наследуется от BaseEntity, и тогда она сама умеет сохранять, искать и удалять себя. Как будто объект сам по себе умный.

      const user = new User();
      user.name = 'Алиса';
      await user.save(); // И всё, сохранилось! Метод прямо у объекта.

      Удобно для мелких проектов, но в больших может превратиться в пиздопроебибну, потому что логика работы с базой размазана по всем объектам.

    2. Data Mapper: А вот это, чувак, более правильный и гибкий путь. Тут вся работа с базой идёт через отдельные штуки — репозитории (Repository). Сущность — это просто данные, а репозиторий — это уже менеджер, который с этими данными работает. Так код чище и тестировать проще.

      // Где-нибудь в сервисе
      const userRepository = dataSource.getRepository(User); // Получаем репозиторий для User
      const newUser = userRepository.create({ name: 'Боб', email: 'bob@mail.com' }); // Создаём инстанс
      await userRepository.save(newUser); // И сохраняем через репозиторий

      Большинство адекватных проектов идут этим путём. Доверия к нему больше, ебать.

  • Связи (Relations): Ну а как же без них? Один-ко-многим, многие-ко-многим — всё это настраивается декораторами (@OneToMany, @ManyToOne и т.д.). TypeORM потом сам джойны строит, если правильно настроить.

  • Миграции (Migrations): Овердохуища важная вещь! Это как система контроля версий, но для структуры базы данных. Хочешь добавить колонку или переименовать таблицу? Пишешь миграцию, применяешь её — и у всей команды база синхронизируется. Без этого — пиши пропало, рано или поздно.

  • Query Builder: Когда стандартных методов репозитория не хватает для какой-нибудь хитрой жопы с агрегациями и сложными условиями, на помощь приходит конструктор запросов. Мощная штука, но надо головой думать.

Вот тебе живой пример, как это может выглядеть в каком-нибудь Express-сервисе:

// user.service.ts
import { AppDataSource } from './data-source'; // Это наш уже настроенный источник данных
import { User } from '../entity/User';

export class UserService {
  // Сразу получаем репозиторий для работы с пользователями
  private userRepository = AppDataSource.getRepository(User);

  async createUser(userData: Partial<User>): Promise<User> {
    // Создаём объект пользователя из данных
    const user = this.userRepository.create(userData);
    // И сохраняем его в базу
    return await this.userRepository.save(user);
  }

  async findUserByEmail(email: string): Promise<User | null> {
    // Ищем одного пользователя по почте
    return await this.userRepository.findOne({ where: { email } });
  }
}

Короче, инструмент серьёзный. Если его освоить и не косячить с архитектурой, то жизнь с базами данных становится в разы проще. Главное — сразу приучай себя к Data Mapper и миграциям, чтобы потом не было мучительно больно.