← Назад к вопросам

Что такое TypeORM?

1.0 Junior🔥 161 комментариев
#Базы данных и SQL#Фреймворки и библиотеки

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

TypeORM: Object-Relational Mapping для Node.js

TypeORM — это популярный ORM фреймворк для Node.js и TypeScript, который упрощает работу с базами данных, предоставляя объектно-ориентированный интерфейс к SQL таблицам.

Что такое ORM?

ORM (Object-Relational Mapping) — это техника, которая автоматически переводит объекты из кода в строки БД и обратно:

// SQL таблица
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255),
  email VARCHAR(255),
  created_at TIMESTAMP
);

// TypeORM Entity (объект)
@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ type: 'varchar' })
  name: string;

  @Column({ type: 'varchar' })
  email: string;

  @CreateDateColumn()
  createdAt: Date;
}

// Работа с кодом (как с объектами)
const user = new User();
user.name = 'John';
user.email = 'john@example.com';
await userRepository.save(user); // Автоматически INSERT в БД

Основные возможности TypeORM

1. Декораторы для описания сущностей

@Entity('posts')
export class Post {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ type: 'varchar', length: 255 })
  title: string;

  @Column({ type: 'text', nullable: true })
  content: string;

  @Column({ type: 'int', default: 0 })
  viewCount: number;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @Column({ type: 'boolean', default: true })
  isPublished: boolean;
}

2. Отношения между таблицами (Relations)

// One-to-Many отношение
@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  name: string;

  @OneToMany(() => Post, post => post.author)
  posts: Post[];
}

@Entity('posts')
export class Post {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  title: string;

  @ManyToOne(() => User, user => user.posts)
  @JoinColumn({ name: 'author_id' })
  author: User;
}

// Many-to-Many отношение
@Entity('students')
export class Student {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToMany(() => Course)
  @JoinTable() // Создаёт junction table
  courses: Course[];
}

@Entity('courses')
export class Course {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToMany(() => Student, student => student.courses)
  students: Student[];
}

3. QueryBuilder — мощный конструктор запросов

// Вместо написания SQL запросов
const users = await userRepository
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'post')
  .where('user.id = :id', { id: userId })
  .andWhere('post.isPublished = :isPublished', { isPublished: true })
  .orderBy('post.createdAt', 'DESC')
  .limit(10)
  .getMany();

// Эквивалентно SQL:
// SELECT user.*, post.* FROM users user
// LEFT JOIN posts post ON user.id = post.author_id
// WHERE user.id = $1 AND post.is_published = true
// ORDER BY post.created_at DESC
// LIMIT 10

4. Миграции для версионирования схемы БД

// Создание миграции
import { MigrationInterface, QueryRunner, Table } from 'typeorm';

export class CreateUsersTable1234567890 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.createTable(
      new Table({
        name: 'users',
        columns: [
          {
            name: 'id',
            type: 'uuid',
            isPrimary: true,
          },
          {
            name: 'email',
            type: 'varchar',
            isUnique: true,
          },
          {
            name: 'password',
            type: 'varchar',
          },
        ],
      }),
      true,
    );
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.dropTable('users');
  }
}

Плюсы TypeORM

Типизация

  • Полная типизация сущностей
  • Автодополнение в IDE
  • Ловит ошибки на этапе компиляции

Меньше SQL кода

  • Не нужно писать запросы вручную
  • Защита от SQL injection
  • Кроссплатформенность (PostgreSQL, MySQL, SQLite)
// Вместо
const query = 'SELECT * FROM users WHERE id = $1';
const result = await db.query(query, [userId]);

// Пишешь
const user = await userRepository.findOne({ where: { id: userId } });

Синхронизация с БД

  • Модели — source of truth
  • Миграции генерируются автоматически
  • Легко отслеживать изменения схемы

Hooks (жизненный цикл сущностей)

@Entity()
export class User {
  @Column()
  email: string;

  @Column()
  password: string;

  @BeforeInsert()
  @BeforeUpdate()
  hashPassword() {
    this.password = bcrypt.hashSync(this.password, 10);
  }
}

Минусы TypeORM

Производительность

  • ORM добавляет overhead
  • Может генерировать неоптимальные запросы
  • N+1 problem — нужно использовать eager loading
// ❌ N+1 problem
const users = await userRepository.find();
for (const user of users) {
  const posts = await postRepository.find({ where: { author: user } });
  // 1 запрос для users + N запросов для каждого user = N+1 queries!
}

// ✅ Решение: eager loading
const users = await userRepository.find({
  relations: ['posts']
});

Сложные запросы

  • QueryBuilder может быть verbose
  • Для сложных запросов лучше писать raw SQL
// Сложный QueryBuilder становится нечитаемым
const stats = await userRepository
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'post')
  .leftJoinAndSelect('post.comments', 'comment')
  .where('user.createdAt > :date', { date: new Date('2024-01-01') })
  .andWhere('post.isPublished = true')
  .andWhere('comment.score > :score', { score: 5 })
  .groupBy('user.id')
  .having('COUNT(post.id) > :postCount', { postCount: 5 })
  .orderBy('user.createdAt', 'DESC')
  .getRawMany();

// Лучше написать raw SQL
const stats = await dataSource.query(`
  SELECT u.id, u.name, COUNT(p.id) as post_count
  FROM users u
  LEFT JOIN posts p ON u.id = p.author_id
  LEFT JOIN comments c ON p.id = c.post_id
  WHERE u.created_at > $1 AND p.is_published = true AND c.score > $2
  GROUP BY u.id
  HAVING COUNT(p.id) > $3
  ORDER BY u.created_at DESC
`, [new Date('2024-01-01'), 5, 5]);

Кривая обучения

  • Много концепций (Entity, Repository, QueryBuilder, Migrations)
  • Документация местами слабая
  • Ошибки иногда непонятные

Alternatives

Prisma — более современный ORM

const user = await prisma.user.findUnique({
  where: { id: userId },
  include: { posts: true },
});

Sequelize — классический ORM для Node.js

Raw SQL — для максимального контроля

Когда использовать TypeORM?

Используй TypeORM когда:

  • TypeScript проект
  • Множество таблиц с отношениями
  • Стандартные CRUD операции
  • Нужна типизация

Избегай когда:

  • Очень сложные запросы
  • Нужна максимальная производительность
  • Микросервисная архитектура (лучше raw SQL)
  • Документ-ориентированная БД (MongoDB)

Пример полного приложения

// Entity
@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  email: string;

  @OneToMany(() => Post, post => post.author)
  posts: Post[];
}

// Service
@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}

  async findById(id: string): Promise<User> {
    return this.userRepository.findOne({
      where: { id },
      relations: ['posts'],
    });
  }

  async create(email: string): Promise<User> {
    const user = this.userRepository.create({ email });
    return this.userRepository.save(user);
  }
}

// Controller
@Controller('users')
export class UserController {
  constructor(private userService: UserService) {}

  @Get(':id')
  getUser(@Param('id') id: string) {
    return this.userService.findById(id);
  }
}