JS Tower
Node.jsМодуль 5: Базы данных

Отношения и связи

One-to-Many, Many-to-Many, populate, join

Цель урока

В этом уроке ты научишься:

  • Создавать связи между коллекциями/таблицами
  • Использовать populate в Mongoose
  • Работать с JOIN в SQL

Типы связей

ТипОписаниеПример
One-to-OneОдин к одномуUser → Profile
One-to-ManyОдин ко многимUser → Posts
Many-to-ManyМногие ко многимPost → Tags

MongoDB (Mongoose)

One-to-Many

// schemas/user.js
const userSchema = new mongoose.Schema({
  name: String,
  email: String
});

// schemas/post.js
const postSchema = new mongoose.Schema({
  title: String,
  content: String,
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  }
});

// Виртуальное поле для обратной связи
userSchema.virtual('posts', {
  ref: 'Post',
  localField: '_id',
  foreignField: 'author'
});

Populate

// Получить пост с автором
const post = await Post.findById(id).populate('author');
// { _id: ..., title: '...', author: { _id: ..., name: 'Иван' } }

// Выбор полей
const post = await Post.findById(id).populate('author', 'name email');

// Множественный populate
const post = await Post.findById(id)
  .populate('author')
  .populate('comments');

// Вложенный populate
const post = await Post.findById(id)
  .populate({
    path: 'comments',
    populate: { path: 'author', select: 'name' }
  });

// Получить пользователя с постами
const user = await User.findById(id).populate('posts');

Many-to-Many

// schemas/post.js
const postSchema = new mongoose.Schema({
  title: String,
  tags: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Tag'
  }]
});

// schemas/tag.js
const tagSchema = new mongoose.Schema({
  name: { type: String, unique: true }
});

// Использование
const post = await Post.findById(id).populate('tags');

// Создание с тегами
const post = await Post.create({
  title: 'Мой пост',
  tags: [tagId1, tagId2]
});

// Добавление тега
await Post.findByIdAndUpdate(id, {
  $push: { tags: newTagId }
});

PostgreSQL (Prisma)

One-to-Many

model User {
  id    Int    @id @default(autoincrement())
  name  String
  posts Post[]
}

model Post {
  id       Int    @id @default(autoincrement())
  title    String
  author   User   @relation(fields: [authorId], references: [id])
  authorId Int
}
// Получить пост с автором
const post = await prisma.post.findUnique({
  where: { id: 1 },
  include: { author: true }
});

// Получить пользователя с постами
const user = await prisma.user.findUnique({
  where: { id: 1 },
  include: { posts: true }
});

// Создать пост для пользователя
const post = await prisma.post.create({
  data: {
    title: 'Мой пост',
    author: { connect: { id: 1 } }
  }
});

// Создать пользователя с постами
const user = await prisma.user.create({
  data: {
    name: 'Иван',
    posts: {
      create: [
        { title: 'Пост 1' },
        { title: 'Пост 2' }
      ]
    }
  }
});

Many-to-Many

model Post {
  id    Int    @id @default(autoincrement())
  title String
  tags  Tag[]
}

model Tag {
  id    Int    @id @default(autoincrement())
  name  String @unique
  posts Post[]
}
// Создать пост с тегами
const post = await prisma.post.create({
  data: {
    title: 'Мой пост',
    tags: {
      connectOrCreate: [
        { where: { name: 'JavaScript' }, create: { name: 'JavaScript' } },
        { where: { name: 'Node.js' }, create: { name: 'Node.js' } }
      ]
    }
  },
  include: { tags: true }
});

// Добавить тег к посту
await prisma.post.update({
  where: { id: 1 },
  data: {
    tags: { connect: { id: 5 } }
  }
});

// Удалить тег из поста
await prisma.post.update({
  where: { id: 1 },
  data: {
    tags: { disconnect: { id: 5 } }
  }
});

Практика

Задание: Блог со связями

Задача: Создай модели для блога: User, Post, Comment.

Решение (Mongoose):

// User
const userSchema = new mongoose.Schema({
  name: String,
  email: String
});

userSchema.virtual('posts', {
  ref: 'Post',
  localField: '_id',
  foreignField: 'author'
});

// Post
const postSchema = new mongoose.Schema({
  title: String,
  content: String,
  author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }]
});

// Comment
const commentSchema = new mongoose.Schema({
  text: String,
  author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  post: { type: mongoose.Schema.Types.ObjectId, ref: 'Post' }
});

// Запрос
const post = await Post.findById(id)
  .populate('author', 'name')
  .populate({
    path: 'comments',
    populate: { path: 'author', select: 'name' }
  });

Проверь себя

  1. Что делает populate в Mongoose?
  2. Как создать Many-to-Many связь в Prisma?
  3. Что такое виртуальное поле?