При розробці на Node.js і TypeScript багато хто стикається з однією і тією ж болем: ручне написання SQL-запитів і синхронізація схеми з кодом. Помилка в SQL-запиті — і production лежить. TypeORM вирішує цю проблему, але його налаштування вимагає врахування десятка нюансів: від конфігурації підключення до генерації міграцій. Ми допоможемо вам налаштувати TypeORM під ваш проєкт — від простого сервера до складного NestJS-застосунку. Наші інженери мають понад 10 років досвіду роботи з реляційними базами даних і гарантують стабільність вашої схеми.
Чому стандартне налаштування TypeORM часто призводить до проблем?
N+1 запит TypeORM — класична проблема при роботі з ORM. Без оптимізації завантаження 100 статей з авторами породить 101 запит. TypeORM дозволяє легко налаштувати жадібне завантаження через relations або використовувати Query Builder для точного контролю. Ще одна біль — synchronize: true в production: це призводить до втрати даних при зміні схеми. Правильне налаштування міграцій з генерацією diff-файлів — єдиний безпечний спосіб оновлювати базу. Третя проблема — продуктивність: неоптимізовані запити створюють зайве навантаження. TypeORM дає інструменти для моніторингу та тонкого налаштування: subscribers для кешування, пул підключень, логування повільних запитів. При типовому навантаженні 10 000 запитів на хвилину грамотне налаштування знижує кількість запитів на 60%.
Як ми налаштовуємо TypeORM: розбір кейсу
Наш клієнт мав проєкт з PostgreSQL і навантаженням 10 000 запитів на хвилину. Ми зіткнулися з падінням продуктивності через N+1 при завантаженні пов'язаних тегів. Рішення: використали Query Builder з leftJoinAndSelect і пагінацію через skip/take. Додатково налаштували пул з'єднань з max: 20 і idleTimeoutMillis: 30000, що знизило час відповіді на 40%. Використання Query Builder дозволило виконати складний запит у 3 рази швидше порівняно із завантаженням через relations. Правильне налаштування пулу з'єднань заощадило клієнту близько 150 000 грн на рік на інфраструктурі. Замовте налаштування TypeORM — отримайте консультацію інженера протягом години.
// db/data-source.ts
import 'reflect-metadata'
import { DataSource } from 'typeorm'
import { User } from './entities/User'
import { Post } from './entities/Post'
export const AppDataSource = new DataSource({
type: 'postgres',
url: process.env.DATABASE_URL,
entities: [User, Post],
migrations: ['dist/db/migrations/*.js'],
migrationsTableName: 'migrations',
synchronize: false, // НІКОЛИ true в production
logging: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
extra: {
max: 20,
idleTimeoutMillis: 30000,
}
})
// Ініціалізація
await AppDataSource.initialize()
Що входить в налаштування TypeORM?
Процес роботи проходить п'ять етапів:
- Аналітика — вивчаємо поточну схему БД, навантаження та профіль запитів.
- Проектування — обираємо патерн (Active Record або Data Mapper), проектуємо сутності та зв'язки.
- Реалізація — налаштовуємо підключення, пишемо сутності з декораторами, створюємо міграції та репозиторії.
- Тестування — перевіряємо продуктивність, відсутність N+1 та коректність міграцій.
- Деплой та моніторинг — розгортаємо на production, налаштовуємо логування та алерти.
У результат налаштування входить:
- Конфігурація DataSource з пулом та SSL (під будь-яку БД: PostgreSQL, MySQL, SQLite).
- Репозиторії та сутності з індексами та зв'язками.
- Міграції (генерація, запуск, відкат).
- Query Builder для типових запитів.
- Інтеграція з NestJS (модуль, сервіси, контролери).
- Підписки (subscribers) для кешування та сповіщень.
- Документація по API та архітектурі.
- Гарантія на 30 днів після здачі — виправляємо помилки безкоштовно.
Порівняння патернів та вибір сутності
| Характеристика | Active Record | Data Mapper |
|---|---|---|
| Розділення логіки | Дані та поведінка разом | Дані в сутності, поведінка в репозиторіях |
| Складність | Низька, підходить для простих CRUD | Висока, вимагає більше коду |
| Тестування | Складніше через прямі звернення до БД | Легше завдяки репозиторіям |
| Гнучкість | Обмежена | Висока, легко змінювати запити |
| Популярність | У невеликих проєктах | У великих enterprise-рішеннях |
Наші інженери завжди рекомендують Data Mapper для проєктів з бізнес-логікою, оскільки він дає більше контролю та спрощує тестування. Але якщо вам потрібне швидке прототипування — Active Record теж робочий варіант. Правильний вибір сутності критичний для продуктивності. Використовуйте @PrimaryGeneratedColumn('uuid') для розподілених систем, @Index() для полів, які часто фільтруються. Репозиторій — це прошарок між бізнес-логікою та БД. Обгортайте в репозиторій всі запити до сутності: це робить код перевикористовуваним і тестованим.
| Декоратор | Призначення |
|---|---|
@PrimaryGeneratedColumn |
Автоінкрементний UUID або integer |
@Column |
Просте поле з типом та опціями |
@Index |
Індекс для прискорення запитів |
@ManyToOne |
Зв'язок «багато до одного» |
@OneToMany |
Зворотна сторона зв'язку |
Приклад репозиторію з пагінацією:
const postRepository = AppDataSource.getRepository(Post)
async function findPosts(opts: { page: number; limit: number; search?: string }) {
const { page, limit, search } = opts
const qb = postRepository.createQueryBuilder('post')
.leftJoinAndSelect('post.author', 'author')
.leftJoinAndSelect('post.tags', 'tag')
.where('post.published = :published', { published: true })
.orderBy('post.createdAt', 'DESC')
.skip((page - 1) * limit)
.take(limit)
if (search) {
qb.andWhere(
'post.title ILIKE :search OR post.content ILIKE :search',
{ search: `%${search}%` }
)
}
const [items, total] = await qb.getManyAndCount()
return { items, total, pages: Math.ceil(total / limit) }
}
// Складні агрегати
const stats = await AppDataSource.query(`
SELECT
date_trunc('week', created_at) AS week,
count(*) AS posts,
count(*) FILTER (WHERE published = true) AS published
FROM posts
WHERE created_at >= now() - interval '90 days'
GROUP BY 1
ORDER BY 1
`)
Чому міграції важливі для продакшену?
Згідно з офіційною документацією TypeORM, міграції — єдиний безпечний спосіб управління схемою в production. Міграції дозволяють версіонувати зміни схеми бази даних і застосовувати їх послідовно. TypeORM вміє автоматично генерувати міграції на основі змін у сутностях. Це економить до 50% часу на розробку міграцій порівняно з ручним написанням SQL. Приклад генерації та застосування:
# Генерація міграції з diff схеми
npx typeorm migration:generate -n AddUserProfile -d dist/db/data-source.js
# Створити пусту міграцію вручну
npx typeorm migration:create -n AddIndexes
# Застосувати
npx typeorm migration:run -d dist/db/data-source.js
# Відкотити останню
npx typeorm migration:revert -d dist/db/data-source.js
// db/migrations/1234567890-AddUserProfile.ts
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddUserProfile1234567890 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE,
bio TEXT,
avatar_url VARCHAR(500),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE profiles`)
}
}
Інтеграція з NestJS та підписки на події
У NestJS налаштування TypeORM зводиться до імпорту TypeOrmModule з конфігурацією. Додатково можна впроваджувати репозиторії через декоратор @InjectRepository. Subscribers (підписки) дозволяють реагувати на зміни сутностей: після вставки оновити пошуковий індекс, після публікації — відправити сповіщення. Це потужний інструмент, якщо не зловживати. Приклад налаштування NestJS:
// app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm'
@Module({
imports: [
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'postgres',
url: config.get('DATABASE_URL'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
migrations: [__dirname + '/db/migrations/*{.ts,.js}'],
migrationsRun: true,
synchronize: false,
})
}),
TypeOrmModule.forFeature([User, Post])
]
})
export class AppModule {}
Типові помилки та як їх уникнути
- Synchronize в production — ніколи. Використовуйте міграції.
- Відсутність індексів — додавайте
@Index()на поля, за якими часто фільтруєте. - Ігнорування пулу з'єднань — налаштуйте
extra.maxтаidleTimeoutMillis. - N+1 запити — використовуйте
relationsабо Query Builder зleftJoinAndSelect. - Зберігання паролів у сутності — використовуйте
@Column({ select: false })та хешуйте в@BeforeInsert.
Орієнтовні терміни та вартість
Базова настройка TypeORM з сутностями, міграціями та репозиторіями: 1–2 дні. Інтеграція в NestJS-проєкт з модулями та тестами: 2–3 дні. Переведення існуючого проєкту з іншого ORM на TypeORM: 3–5 днів. Вартість базової конфігурації TypeORM починається від 35 000 грн і залежить від складності проєкту. Зв'яжіться з нами для оцінки — це безкоштовно і займе не більше години.
TypeORM на GitHub
TypeORM має понад 30 000 зірок на GitHub та понад 400 контриб'юторів, що свідчить про його популярність і активний розвиток.







