С какими проблемами вы сталкивались при работе с TypeORM в Node.js?

«С какими проблемами вы сталкивались при работе с TypeORM в Node.js?» — вопрос из категории ORM, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Работая с TypeORM в Node.js, я столкнулся со следующими сложностями и нашел способы их решения:

  1. Проблема N+1 запроса: Самая частая проблема при ленивой загрузке связей ({eager: false}). TypeORM по умолчанию не предупреждает об этом.

    • Решение: Явно использовать leftJoinAndSelect в QueryBuilder или указывать relations в методе find. Для сложных сценариев писал raw SQL через query().
      // Проблема: N+1 запросов
      const users = await userRepository.find();
      for (const user of users) {
      console.log(user.photos); // Отдельный запрос для каждого пользователя!
      }
      // Решение: Загрузка связей сразу
      const usersWithPhotos = await userRepository.find({
      relations: ['photos']
      });
      // Или с QueryBuilder
      const users = await userRepository
      .createQueryBuilder('user')
      .leftJoinAndSelect('user.photos', 'photo')
      .getMany();
  2. Производительность сложных запросов: Сгенерированный SQL для QueryBuilder с множественными join и where иногда был неоптимальным.

    • Решение: Для критичных по производительности endpoint'ов я использовал нативные запросы (manager.query()) или донастраивал индексы в БД, опираясь на EXPLAIN ANALYZE.
  3. Работа с миграциями: Автогенерация миграций через typeorm migration:generate иногда пропускала изменения или генерировала лишние. Особенно это касалось переименования полей.

    • Решение: Я всегда внимательно проверял сгенерированный SQL-файл миграции перед применением. Для сложных изменений (типа изменения типа колонки с данными) писал миграции вручную, используя QueryRunner.
  4. Транзакции и управление подключением: Необходимость явно передавать EntityManager или QueryRunner в транзакцию усложняла архитектуру сервисов.

    • Решение: Я создавал абстракцию — Unit of Work паттерн или использовал вложенные транзакции через dataSource.transaction, который автоматически управляет менеджером.
  5. Типизация с кастомными репозиториями: TypeScript-типы иногда "терялись" при использовании кастомных методов репозитория, если не следовать строгому шаблону из документации.

    • Решение: Я создавал абстрактные базовые репозитории с дженериками и явно указывал типы сущностей.