← Назад к вопросам
Что такое 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);
}
}