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

MongoDB и Mongoose

Работа с MongoDB через Mongoose ODM

Цель урока

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

  • Подключаться к MongoDB
  • Создавать схемы и модели
  • Выполнять CRUD операции

Установка

npm install mongoose

Подключение

const mongoose = require('mongoose');

async function connectDB() {
  try {
    await mongoose.connect(process.env.MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true
    });
    console.log('MongoDB подключена');
  } catch (error) {
    console.error('Ошибка подключения:', error.message);
    process.exit(1);
  }
}

// Обработка событий
mongoose.connection.on('disconnected', () => {
  console.log('MongoDB отключена');
});

module.exports = connectDB;

Схемы и модели

Создание схемы

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'Имя обязательно'],
    trim: true,
    minlength: [2, 'Минимум 2 символа'],
    maxlength: [50, 'Максимум 50 символов']
  },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true,
    match: [/^\S+@\S+\.\S+$/, 'Невалидный email']
  },
  password: {
    type: String,
    required: true,
    minlength: 6,
    select: false  // Не возвращать по умолчанию
  },
  role: {
    type: String,
    enum: ['user', 'admin'],
    default: 'user'
  },
  age: {
    type: Number,
    min: 18,
    max: 120
  },
  isActive: {
    type: Boolean,
    default: true
  },
  tags: [String],
  profile: {
    bio: String,
    avatar: String
  }
}, {
  timestamps: true  // createdAt, updatedAt
});

const User = mongoose.model('User', userSchema);

module.exports = User;

CRUD операции

Create

// Создание одного документа
const user = await User.create({
  name: 'Иван',
  email: 'ivan@example.com',
  password: 'hashedpassword'
});

// Или через new
const user = new User({ name: 'Иван', email: 'ivan@example.com' });
await user.save();

// Создание нескольких
await User.insertMany([
  { name: 'Иван', email: 'ivan@example.com' },
  { name: 'Мария', email: 'maria@example.com' }
]);

Read

// Все документы
const users = await User.find();

// С фильтром
const admins = await User.find({ role: 'admin' });

// Один документ
const user = await User.findById('507f1f77bcf86cd799439011');
const user = await User.findOne({ email: 'ivan@example.com' });

// Выбор полей
const users = await User.find().select('name email');
const users = await User.find().select('-password');

// Сортировка
const users = await User.find().sort({ createdAt: -1 });
const users = await User.find().sort('-createdAt name');

// Пагинация
const users = await User.find()
  .skip(10)
  .limit(10);

// Подсчёт
const count = await User.countDocuments({ role: 'admin' });

Update

// Найти и обновить
const user = await User.findByIdAndUpdate(
  id,
  { name: 'Новое имя' },
  { new: true, runValidators: true }
);

// Обновить несколько
await User.updateMany(
  { isActive: false },
  { $set: { role: 'inactive' } }
);

// Операторы обновления
await User.findByIdAndUpdate(id, {
  $set: { name: 'Иван' },
  $inc: { age: 1 },
  $push: { tags: 'новый' },
  $pull: { tags: 'старый' }
});

Delete

// Удалить один
await User.findByIdAndDelete(id);
await User.deleteOne({ email: 'ivan@example.com' });

// Удалить несколько
await User.deleteMany({ isActive: false });

Операторы запросов

// Сравнение
await User.find({ age: { $gt: 18 } });    // >
await User.find({ age: { $gte: 18 } });   // >=
await User.find({ age: { $lt: 30 } });    // <
await User.find({ age: { $lte: 30 } });   // <=
await User.find({ age: { $ne: 25 } });    // !=

// Логические
await User.find({ $and: [{ age: { $gt: 18 } }, { role: 'user' }] });
await User.find({ $or: [{ role: 'admin' }, { isActive: true }] });

// Массивы
await User.find({ tags: 'javascript' });           // Содержит
await User.find({ tags: { $in: ['js', 'ts'] } }); // Любой из
await User.find({ tags: { $all: ['js', 'ts'] } }); // Все

// Regex
await User.find({ name: /иван/i });
await User.find({ email: { $regex: 'example.com$' } });

Middleware (хуки)

// Перед сохранением
userSchema.pre('save', async function(next) {
  if (this.isModified('password')) {
    this.password = await bcrypt.hash(this.password, 10);
  }
  next();
});

// После сохранения
userSchema.post('save', function(doc) {
  console.log('Пользователь создан:', doc._id);
});

// Перед поиском
userSchema.pre(/^find/, function(next) {
  this.find({ isActive: true });
  next();
});

Методы и статики

// Методы экземпляра
userSchema.methods.comparePassword = async function(password) {
  return bcrypt.compare(password, this.password);
};

const user = await User.findOne({ email }).select('+password');
const isMatch = await user.comparePassword('password123');

// Статические методы
userSchema.statics.findByEmail = function(email) {
  return this.findOne({ email });
};

const user = await User.findByEmail('ivan@example.com');

Виртуальные поля

userSchema.virtual('fullName').get(function() {
  return `${this.firstName} ${this.lastName}`;
});

// Включить в JSON
userSchema.set('toJSON', { virtuals: true });

Практика

Задание: Модель Post

Задача: Создай модель для постов блога.

Решение:

const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
    trim: true
  },
  content: {
    type: String,
    required: true
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  tags: [String],
  likes: {
    type: Number,
    default: 0
  },
  isPublished: {
    type: Boolean,
    default: false
  }
}, { timestamps: true });

module.exports = mongoose.model('Post', postSchema);

Проверь себя

  1. Что делает select: false в схеме?
  2. Как выполнить пагинацию?
  3. Что такое middleware в Mongoose?