MongoDB: основы и Mongoose
MongoDB — документоориентированная NoSQL база данных, хранящая данные в BSON-документах (похоже на JSON); Mongoose — ODM-библиотека для Node.js, добавляющая схемы, валидацию и удобный API поверх MongoDB.
Зачем нужно
MongoDB хорошо подходит для гибких, часто меняющихся структур данных: нет необходимости в миграциях при добавлении полей, хранение вложенных объектов и массивов естественно. Mongoose добавляет типизацию и валидацию на уровне приложения, что компенсирует отсутствие схемы в MongoDB.
Где используется
- Хранение пользовательских профилей с произвольными атрибутами
- Каталоги товаров (разные атрибуты для разных категорий)
- Логи и события (append-only коллекции)
- Прототипирование — быстрый старт без миграций
Основной контент
Установка и подключение
npm install mongoose
// db/connect.js
const mongoose = require('mongoose');
async function connectDB() {
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB connected');
}
mongoose.connection.on('error', (err) => console.error('MongoDB error:', err));
mongoose.connection.on('disconnected', () => console.warn('MongoDB disconnected'));
module.exports = connectDB;
Определение схемы и модели
// models/User.js
const mongoose = require('mongoose');
const { Schema } = mongoose;
const userSchema = new Schema(
{
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
age: { type: Number, min: 0, max: 150 },
role: { type: String, enum: ['user', 'admin'], default: 'user' },
tags: [String], // массив строк
address: { // вложенный объект
city: String,
country: { type: String, default: 'RU' }
},
createdAt: { type: Date, default: Date.now() }
},
{ timestamps: true } // автоматически добавит createdAt, updatedAt
);
// Индексы
userSchema.index({ email: 1 }, { unique: true });
userSchema.index({ name: 'text' }); // full-text search
// Метод экземпляра
userSchema.methods.toPublicJSON = function {
const obj = this.toObject;
delete obj.password;
return obj;
};
// Статический метод
userSchema.statics.findByEmail = function (email) {
return this.findOne({ email });
};
const User = mongoose.model('User', userSchema);
module.exports = User;
CRUD операции
const User = require('./models/User');
// Create
const user = await User.create({ name: 'Alice', email: 'alice@example.com' });
// Read
const users = await User.find({ role: 'admin' });
const user = await User.findById('64a1b2c3d4e5f6789abcdef0');
const user = await User.findOne({ email: 'alice@example.com' });
// Проекция и сортировка
const users = await User.find({})
.select('name email -_id') // включить/исключить поля
.sort({ createdAt: -1 })
.limit(10)
.skip(20);
// Update
await User.findByIdAndUpdate(id, { name: 'Bob' }, { new: true, runValidators: true });
await User.updateMany({ role: 'user' }, { $set: { active: true } });
// Delete
await User.findByIdAndDelete(id);
await User.deleteMany({ active: false });
Populate — связи между коллекциями
const postSchema = new Schema({
title: String,
author: { type: Schema.Types.ObjectId, ref: 'User' } // ссылка
});
const Post = mongoose.model('Post', postSchema);
// Заполнить связанные документы
const posts = await Post.find.populate('author', 'name email');
Частые ошибки
- Не закрывать соединение в тестах — Mongoose держит соединение открытым;
afterAll( => mongoose.connection.close()) - Использовать
_idкак строку без приведения типа —User.findById('abc')— mongoose сам конвертирует, ноUser.findOne({ _id: 'abc' })требуетnew mongoose.Types.ObjectId('abc') - Не настроить
runValidatorsприupdate— по умолчанию валидация схемы не выполняется приfindByIdAndUpdate - Хранить всё в одной коллекции — MongoDB плохо справляется с JOIN-запросами; нормализовывать данные с умом