Перейти к основному содержимому

TypeORM

Разработчику Архитектору

Дальше: TypeScript и Node.js · Декораторы · Обработка ошибок


TypeORM — ORM для TypeScript/JavaScript: таблицы описывают классами-сущностями, запросы — через Repository и QueryBuilder. Типизация полей сущности проверяется TS; SQL и связи задаются декораторами или схемой Data API.

Маршрут: TypeScript и Node.jsTypeORM. Нужны декораторы и experimentalDecorators в legacy-стеке.

Альтернативы: Prisma (schema-first), Drizzle (SQL-like types). TypeORM выбран как распространённый в NestJS-стеке.


Что даёт ORM

Без ORMС TypeORM
SQL строки вручнуюметоды репозитория
Расхождение полей и таблицыкласс Entity
Миграции отдельноCLI миграций (при настройке)

TS не заменяет миграции и индексы — схему БД проектируют осознанно.


Минимальная сущность

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

@Entity("users")
export class User {
@PrimaryGeneratedColumn("uuid")
id!: string;

@Column({ unique: true })
email!: string;

@Column()
name!: string;
}

Разбор:

  • @Entity("users") — имя таблицы.
  • ! — поле инициализирует БД/ORM, не конструктор — 23.md.

Инициализация DataSource

import { DataSource } from "typeorm";
import { User } from "./entity/User.js";

export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "app",
password: "secret",
database: "app_db",
entities: [User],
synchronize: false, // только dev с осторожностью!
migrations: ["dist/migrations/*.js"],
});

await AppDataSource.initialize();

Разбор:

  • synchronize: true удобно в dev, опасно в prod — используйте миграции.
  • Секреты — из env — 22.md.

Repository

const repo = AppDataSource.getRepository(User);

async function createUser(email: string, name: string): Promise<User> {
const user = repo.create({ email, name });
return repo.save(user);
}

async function findUser(id: string): Promise<User | null> {
return repo.findOneBy({ id });
}

Разбор:

  • findOneByUser | null — обработайте null — 27.md.
  • create + save — два шага; можно insert для bulk.

Связь one-to-many

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

@Column()
title!: string;

@ManyToOne(() => User, (user) => user.posts)
author!: User;
}

@Entity("users")
export class User {
@PrimaryGeneratedColumn("uuid")
id!: string;

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

Разбор:

  • () => User — ленивая ссылка на класс (циклический import).
  • Загрузка связей: relations: { posts: true } в find.

DTO vs Entity

type CreateUserDto = { email: string; name: string };

async function register(dto: CreateUserDto): Promise<User> {
return createUser(dto.email, dto.name);
}

Не экспортируйте Entity напрямую в API-ответ — маппер toDto(user)22.md, 28.md.


Миграции

npx typeorm migration:generate -d src/data-source.ts src/migrations/AddUserIndex
npx typeorm migration:run -d src/data-source.ts

Разбор:

  • Миграция — версионированное изменение схемы.
  • Генерация сравнивает entities с БД — проверяйте SQL вручную.

Слой сервиса

export class UserService {
constructor(private readonly repo: Repository<User>) {}

async getOrThrow(id: string): Promise<User> {
const user = await this.repo.findOneBy({ id });
if (!user) throw new AppError(`User ${id}`, "NOT_FOUND");
return user;
}
}

Разделение: controllerservicerepository22.md.


Частые ошибки

ОшибкаЧто делать
synchronize: true в prodмиграции
Entity в JSON ответеDTO
N+1 запросовrelations или join
Нет reflect-metadataimport в entry
any в QueryBuilderтипизировать параметры

Практика

  1. Поднимите Postgres (Docker) и сущность User.
  2. Добавьте Post с ManyToOne / OneToMany.
  3. Сгенерируйте и примените миграцию.
  4. Сервис getOrThrow с доменной ошибкой.
  5. Ответ API через DTO, не User entity.

Смежные статьи