NodeJS Back-end Инженер

NodeJS Back-end Инженер

Роадмап навыков для прокачки

Что такое ORM? Назовите популярные ORM для Node.js

DatabasesDatabase common operationsdefault

Основная идея

ORM (Object-Relational Mapping) — это техника программирования, позволяющая работать с базой данных через объекты языка программирования вместо написания SQL-запросов вручную. ORM создаёт "мост" между объектно-ориентированным кодом и реляционной базой данных.

Ключевые аспекты

  • Маппинг — таблицы БД отображаются на классы/модели, строки на объекты
  • Абстракция — скрывает детали SQL, предоставляя API на языке программирования
  • CRUD — автоматическая генерация запросов INSERT, SELECT, UPDATE, DELETE
  • Миграции — управление схемой базы данных через код
  • Связи — работа с отношениями между таблицами (1:1, 1:N, N:M)

Популярные ORM для Node.js

  • Prisma — современный ORM с type-safe клиентом и удобными миграциями
  • TypeORM — поддерживает Active Record и Data Mapper паттерны
  • Sequelize — зрелый ORM с богатой экосистемой
  • Drizzle — легковесный, type-safe ORM
  • Knex.js — query builder (не ORM, но часто используется вместе)

Плюсы

  • Ускорение разработки — меньше boilerplate кода
  • Type safety — проверка типов на этапе компиляции (Prisma, TypeORM)
  • Портируемость — проще переключаться между СУБД

Минусы

  • Overhead производительности на сложных запросах
  • "Магия" — сложнее понять, какой SQL генерируется
  • N+1 проблема при неправильном использовании связей

Частые ошибки на собеседованиях

  • Не знают разницу между ORM и Query Builder
  • Забывают про N+1 проблему и способы её решения (eager loading)
  • Не понимают, когда лучше использовать raw SQL вместо ORM

Введение и проблематика

При разработке backend-приложений постоянно приходится взаимодействовать с базой данных: создавать, читать, обновлять и удалять данные. Традиционный подход — писать SQL-запросы вручную — имеет ряд проблем:

  • Много повторяющегося кода (boilerplate)
  • Риск SQL-инъекций при неправильной обработке параметров
  • Отсутствие проверки типов
  • Сложность поддержки при изменении схемы

ORM (Object-Relational Mapping) решает эти проблемы, предоставляя абстракцию над базой данных и позволяя работать с данными как с обычными объектами.


Базовая теория

Что такое ORM?

ORM (Object-Relational Mapping) — это паттерн проектирования и набор инструментов для преобразования данных между объектно-ориентированными языками программирования и реляционными базами данных.

graph LR subgraph "Приложение" A[User объект] B[Post объект] end subgraph "ORM" C[Маппинг] end subgraph "База данных" D[users таблица] E[posts таблица] end A <--> C B <--> C C <--> D C <--> E

Как работает маппинг?

Концепция БДКонцепция OOP
ТаблицаКласс / Модель
СтрокаОбъект / Экземпляр
КолонкаСвойство / Поле
Foreign KeyСвязь / Отношение

Популярные ORM для Node.js

Prisma — современный ORM с акцентом на Developer Experience

  • Type-safe клиент: автогенерация TypeScript типов из схемы
  • Prisma Studio: GUI для работы с данными
  • Миграции: автоматическое отслеживание изменений схемы
typescript
// schema.prisma
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}
 
model Post {
  id       Int    @id @default(autoincrement())
  title    String
  author   User   @relation(fields: [authorId], references: [id])
  authorId Int
}
typescript
// Использование Prisma Client
import { PrismaClient } from '@prisma/client';
 
const prisma = new PrismaClient();
 
// Создание пользователя с постом
const user = await prisma.user.create({
  data: {
    email: 'alice@example.com',
    name: 'Alice',
    posts: {
      create: { title: 'Hello World' }
    }
  },
  include: { posts: true } // Eager loading
});

Практические примеры

CRUD операции с Prisma

typescript
import { PrismaClient } from '@prisma/client';
 
const prisma = new PrismaClient();
 
// CREATE
const newUser = await prisma.user.create({
  data: { email: 'bob@example.com', name: 'Bob' }
});
 
// READ
const user = await prisma.user.findUnique({
  where: { email: 'bob@example.com' }
});
 
const allUsers = await prisma.user.findMany({
  where: { name: { contains: 'ob' } },
  orderBy: { createdAt: 'desc' },
  take: 10
});
 
// UPDATE
const updatedUser = await prisma.user.update({
  where: { id: 1 },
  data: { name: 'Robert' }
});
 
// DELETE
await prisma.user.delete({
  where: { id: 1 }
});

Сравнение: ORM vs Raw SQL

typescript
// Raw SQL (pg library)
const result = await pool.query(
  'SELECT u.*, p.title FROM users u LEFT JOIN posts p ON u.id = p.author_id WHERE u.email = $1',
  ['alice@example.com']
);
 
// Prisma ORM
const user = await prisma.user.findUnique({
  where: { email: 'alice@example.com' },
  include: { posts: true }
});

Пограничные кейсы

⚠️

N+1 проблема: При загрузке связанных данных без eager loading ORM выполняет отдельный запрос для каждой связи.

typescript
// ❌ N+1 проблема
const users = await prisma.user.findMany();
for (const user of users) {
  // Каждая итерация = отдельный запрос к БД!
  const posts = await prisma.post.findMany({
    where: { authorId: user.id }
  });
}
 
// ✅ Решение: eager loading
const users = await prisma.user.findMany({
  include: { posts: true } // Один JOIN запрос
});
⚠️

Сложные запросы: Для сложной аналитики и отчётов ORM может генерировать неоптимальный SQL. В таких случаях лучше использовать raw queries.

typescript
// Сложный запрос — лучше raw SQL
const result = await prisma.$queryRaw`
  SELECT
    DATE_TRUNC('month', created_at) as month,
    COUNT(*) as count,
    SUM(amount) as total
  FROM orders
  WHERE created_at > ${startDate}
  GROUP BY DATE_TRUNC('month', created_at)
  ORDER BY month
`;

Плюсы и минусы ORM

АспектПлюсыМинусы
Скорость разработкиБыстрее писать CRUDИзучение API ORM
БезопасностьЗащита от SQL-инъекций
ТипизацияType-safe запросы
ПроизводительностьOverhead на простых запросах
Сложные запросыГенерирует неоптимальный SQL
ПортируемостьПроще менять СУБДVendor-specific фичи недоступны
Отладка"Магия", сложнее понять SQL

Вопросы интервьюера

Q: Когда лучше использовать raw SQL вместо ORM?

Для сложных аналитических запросов, отчётов, bulk операций и когда нужна максимальная производительность. ORM хорош для типичных CRUD операций.

Q: Что такое Query Builder и чем он отличается от ORM?

Query Builder (Knex.js) — это инструмент для программного построения SQL-запросов без маппинга на объекты. ORM добавляет слой абстракции с моделями и связями.

Q: Как решить N+1 проблему?

Использовать eager loading (include/relations), batch loading (DataLoader), или переписать на JOIN с raw SQL.

Q: Какой ORM выбрать для нового проекта на Node.js?

Prisma — для TypeScript проектов с фокусом на DX. TypeORM — для сложных enterprise-проектов. Sequelize — если нужна зрелая экосистема.


Источники