Node.js MongoDB 连接

章节概述

MongoDB 是一种流行的 NoSQL 数据库,它使用文档模型存储数据,具有灵活、可扩展的特点。在 Node.js 应用中,我们通常使用 Mongoose ODM(对象文档映射)库来与 MongoDB 交互,它提供了一种直观的方式来定义数据模型和执行数据库操作。本集将详细介绍如何在 Node.js 中连接 MongoDB,使用 Mongoose 定义数据模型,并实现 CRUD 操作。

核心知识点讲解

1. MongoDB 简介

MongoDB 是一个基于分布式文件存储的 NoSQL 数据库,它的主要特点包括:

  • 文档模型:使用 BSON(Binary JSON)格式存储数据,类似于 JSON 文档
  • 灵活的模式:不需要预定义表结构,文档可以有不同的字段和结构
  • 可扩展性:支持水平扩展,可以通过分片来处理大量数据
  • 强大的查询功能:支持丰富的查询操作,包括索引、聚合等
  • 高可用性:支持副本集,提供数据冗余和自动故障转移

2. Mongoose ODM 简介

Mongoose 是一个 Node.js 库,它提供了对 MongoDB 的对象文档映射(ODM)功能,使得在 Node.js 中使用 MongoDB 更加方便。Mongoose 的主要特点包括:

  • 模式定义:允许定义数据模型的结构和验证规则
  • 中间件:支持在保存、更新等操作前后执行自定义逻辑
  • 查询构建器:提供链式调用的查询构建器,使得查询更加直观
  • 虚拟属性:支持定义不在数据库中存储的虚拟属性
  • 数据验证:内置数据验证功能,确保数据的完整性

3. 连接 MongoDB

首先,需要安装 Mongoose 模块:

npm install mongoose

3.1 基本连接

const mongoose = require('mongoose');

// 连接到 MongoDB 数据库
mongoose.connect('mongodb://localhost:27017/mydatabase', {
  useNewUrlParser: true,
  useUnifiedTopology: true
})
.then(() => {
  console.log('成功连接到 MongoDB');
})
.catch((err) => {
  console.error('连接 MongoDB 失败:', err);
});

3.2 连接选项

Mongoose 提供了多种连接选项,用于配置连接行为:

  • useNewUrlParser:使用新的 URL 解析器
  • useUnifiedTopology:使用新的服务器发现和监控引擎
  • useCreateIndex:使用 createIndex 而不是 ensureIndex
  • useFindAndModify:使用 findOneAndUpdate 而不是 findAndModify
  • autoIndex:自动创建索引
  • poolSize:连接池大小
  • bufferMaxEntries:缓冲区最大条目数

3.3 连接事件

Mongoose 连接对象支持多种事件:

const mongoose = require('mongoose');

// 获取默认连接
const db = mongoose.connection;

// 连接成功事件
db.on('connected', () => {
  console.log('Mongoose 已连接到数据库');
});

// 连接错误事件
db.on('error', (err) => {
  console.error('Mongoose 连接错误:', err);
});

// 连接断开事件
db.on('disconnected', () => {
  console.log('Mongoose 已断开连接');
});

// 应用终止时关闭连接
process.on('SIGINT', () => {
  db.close(() => {
    console.log('Mongoose 连接已关闭(应用终止)');
    process.exit(0);
  });
});

// 连接到 MongoDB
mongoose.connect('mongodb://localhost:27017/mydatabase', {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

4. 定义 Schema 和 Model

在 Mongoose 中,Schema 定义了文档的结构,而 Model 是基于 Schema 编译的构造函数,用于创建和操作文档。

4.1 定义 Schema

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// 定义用户 Schema
const userSchema = new Schema({
  username: {
    type: String,
    required: true,
    unique: true,
    trim: true
  },
  email: {
    type: String,
    required: true,
    unique: true,
    trim: true,
    lowercase: true
  },
  password: {
    type: String,
    required: true
  },
  age: {
    type: Number,
    min: 0
  },
  role: {
    type: String,
    enum: ['user', 'admin'],
    default: 'user'
  },
  createdAt: {
    type: Date,
    default: Date.now
  },
  updatedAt: {
    type: Date,
    default: Date.now
  }
});

// 定义中间件,在保存前更新 updatedAt 字段
userSchema.pre('save', function(next) {
  this.updatedAt = new Date();
  next();
});

// 定义虚拟属性
userSchema.virtual('fullName').get(function() {
  // 假设还有 firstName 和 lastName 字段
  return `${this.firstName} ${this.lastName}`;
});

// 定义方法
userSchema.methods.greet = function() {
  return `Hello, ${this.username}!`;
};

// 定义静态方法
userSchema.statics.findByEmail = function(email) {
  return this.findOne({ email });
};

4.2 创建 Model

// 基于 Schema 创建 Model
const User = mongoose.model('User', userSchema);

// 导出 Model
module.exports = User;

5. CRUD 操作

CRUD 是指创建(Create)、读取(Read)、更新(Update)和删除(Delete)操作,这是数据库操作的基本功能。

5.1 创建(Create)

// 创建单个文档
const user = new User({
  username: 'john',
  email: 'john@example.com',
  password: 'password123',
  age: 30
});

user.save()
.then((result) => {
  console.log('用户创建成功:', result);
})
.catch((err) => {
  console.error('用户创建失败:', err);
});

// 批量创建文档
User.insertMany([
  {
    username: 'jane',
    email: 'jane@example.com',
    password: 'password123',
    age: 25
  },
  {
    username: 'bob',
    email: 'bob@example.com',
    password: 'password123',
    age: 35
  }
])
.then((result) => {
  console.log('用户批量创建成功:', result);
})
.catch((err) => {
  console.error('用户批量创建失败:', err);
});

5.2 读取(Read)

// 查找所有文档
User.find()
.then((users) => {
  console.log('所有用户:', users);
})
.catch((err) => {
  console.error('查找用户失败:', err);
});

// 查找单个文档
User.findOne({ username: 'john' })
.then((user) => {
  console.log('找到用户:', user);
})
.catch((err) => {
  console.error('查找用户失败:', err);
});

// 根据 ID 查找文档
User.findById('60a7c0a9b1c0f0a8b8c0a0b0')
.then((user) => {
  console.log('找到用户:', user);
})
.catch((err) => {
  console.error('查找用户失败:', err);
});

// 带条件的查找
User.find({ age: { $gt: 25 } })
.then((users) => {
  console.log('年龄大于 25 的用户:', users);
})
.catch((err) => {
  console.error('查找用户失败:', err);
});

// 带排序和分页的查找
User.find()
.sort({ createdAt: -1 }) // 按创建时间倒序
.skip(10) // 跳过前 10 条
.limit(5) // 限制返回 5 条
.then((users) => {
  console.log('分页查询结果:', users);
})
.catch((err) => {
  console.error('查找用户失败:', err);
});

5.3 更新(Update)

// 更新单个文档(使用 findOneAndUpdate)
User.findOneAndUpdate(
  { username: 'john' },
  { age: 31 },
  { new: true } // 返回更新后的文档
)
.then((user) => {
  console.log('用户更新成功:', user);
})
.catch((err) => {
  console.error('用户更新失败:', err);
});

// 更新多个文档
User.updateMany(
  { role: 'user' },
  { $set: { role: 'member' } }
)
.then((result) => {
  console.log('用户批量更新成功:', result);
})
.catch((err) => {
  console.error('用户批量更新失败:', err);
});

// 使用 save 方法更新
User.findOne({ username: 'john' })
.then((user) => {
  if (user) {
    user.age = 32;
    return user.save();
  }
})
.then((user) => {
  console.log('用户更新成功:', user);
})
.catch((err) => {
  console.error('用户更新失败:', err);
});

5.4 删除(Delete)

// 删除单个文档
User.findOneAndDelete({ username: 'john' })
.then((user) => {
  console.log('用户删除成功:', user);
})
.catch((err) => {
  console.error('用户删除失败:', err);
});

// 删除多个文档
User.deleteMany({ age: { $lt: 18 } })
.then((result) => {
  console.log('用户批量删除成功:', result);
})
.catch((err) => {
  console.error('用户批量删除失败:', err);
});

// 根据 ID 删除文档
User.findByIdAndDelete('60a7c0a9b1c0f0a8b8c0a0b0')
.then((user) => {
  console.log('用户删除成功:', user);
})
.catch((err) => {
  console.error('用户删除失败:', err);
});

实用案例分析

案例:用户管理系统

下面我们将使用 Express 和 Mongoose 实现一个完整的用户管理系统,支持用户的注册、登录、查询、更新和删除操作。

项目结构

user-management-system/
├── app.js
├── models/
│   └── User.js
├── routes/
│   └── users.js
├── middlewares/
│   └── auth.js
├── utils/
│   └── password.js
├── package.json
└── .env

安装依赖

npm install express mongoose bcryptjs jsonwebtoken dotenv

配置文件(.env)

# MongoDB 连接字符串
MONGO_URI=mongodb://localhost:27017/user_management

# JWT 密钥
JWT_SECRET=your_jwt_secret_key

# 服务器端口
PORT=3000

密码工具(utils/password.js)

const bcrypt = require('bcryptjs');

// 加密密码
const hashPassword = (password) => {
  return bcrypt.hashSync(password, bcrypt.genSaltSync(10));
};

// 验证密码
const verifyPassword = (password, hashedPassword) => {
  return bcrypt.compareSync(password, hashedPassword);
};

module.exports = {
  hashPassword,
  verifyPassword
};

认证中间件(middlewares/auth.js)

const jwt = require('jsonwebtoken');

// 认证中间件
const auth = (req, res, next) => {
  try {
    // 从请求头获取 token
    const token = req.header('Authorization').replace('Bearer ', '');
    
    // 验证 token
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    
    // 将解码后的用户信息存储到请求对象
    req.user = decoded;
    
    next();
  } catch (error) {
    res.status(401).json({ message: '认证失败,请重新登录' });
  }
};

module.exports = auth;

用户模型(models/User.js)

const mongoose = require('mongoose');
const { hashPassword } = require('../utils/password');

const Schema = mongoose.Schema;

// 定义用户 Schema
const userSchema = new Schema({
  username: {
    type: String,
    required: true,
    unique: true,
    trim: true
  },
  email: {
    type: String,
    required: true,
    unique: true,
    trim: true,
    lowercase: true
  },
  password: {
    type: String,
    required: true
  },
  age: {
    type: Number,
    min: 0
  },
  role: {
    type: String,
    enum: ['user', 'admin'],
    default: 'user'
  },
  createdAt: {
    type: Date,
    default: Date.now
  },
  updatedAt: {
    type: Date,
    default: Date.now
  }
});

// 密码加密中间件
userSchema.pre('save', function(next) {
  // 只有在密码被修改时才加密
  if (this.isModified('password')) {
    this.password = hashPassword(this.password);
  }
  this.updatedAt = new Date();
  next();
});

// 隐藏密码字段
userSchema.methods.toJSON = function() {
  const user = this.toObject();
  delete user.password;
  return user;
};

// 创建 User Model
const User = mongoose.model('User', userSchema);

module.exports = User;

用户路由(routes/users.js)

const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const auth = require('../middlewares/auth');
const { verifyPassword } = require('../utils/password');

// 用户注册
router.post('/register', async (req, res) => {
  try {
    // 创建新用户
    const user = new User(req.body);
    await user.save();
    
    res.status(201).json({ message: '用户注册成功', user });
  } catch (error) {
    res.status(400).json({ message: '用户注册失败', error: error.message });
  }
});

// 用户登录
router.post('/login', async (req, res) => {
  try {
    // 查找用户
    const user = await User.findOne({ email: req.body.email });
    if (!user) {
      return res.status(401).json({ message: '邮箱或密码错误' });
    }
    
    // 验证密码
    if (!verifyPassword(req.body.password, user.password)) {
      return res.status(401).json({ message: '邮箱或密码错误' });
    }
    
    // 生成 JWT token
    const token = jwt.sign(
      { _id: user._id, email: user.email, role: user.role },
      process.env.JWT_SECRET,
      { expiresIn: '7d' }
    );
    
    res.json({ message: '登录成功', token, user });
  } catch (error) {
    res.status(400).json({ message: '登录失败', error: error.message });
  }
});

// 获取当前用户信息
router.get('/me', auth, async (req, res) => {
  try {
    const user = await User.findById(req.user._id);
    res.json({ user });
  } catch (error) {
    res.status(400).json({ message: '获取用户信息失败', error: error.message });
  }
});

// 获取所有用户(仅管理员)
router.get('/', auth, async (req, res) => {
  try {
    // 检查权限
    if (req.user.role !== 'admin') {
      return res.status(403).json({ message: '权限不足' });
    }
    
    const users = await User.find();
    res.json({ users });
  } catch (error) {
    res.status(400).json({ message: '获取用户列表失败', error: error.message });
  }
});

// 更新用户信息
router.put('/:id', auth, async (req, res) => {
  try {
    // 检查权限(只能更新自己的信息或管理员可以更新所有)
    if (req.user.role !== 'admin' && req.user._id !== req.params.id) {
      return res.status(403).json({ message: '权限不足' });
    }
    
    // 更新用户
    const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
    if (!user) {
      return res.status(404).json({ message: '用户不存在' });
    }
    
    res.json({ message: '用户更新成功', user });
  } catch (error) {
    res.status(400).json({ message: '用户更新失败', error: error.message });
  }
});

// 删除用户
router.delete('/:id', auth, async (req, res) => {
  try {
    // 检查权限(只能删除自己的账号或管理员可以删除所有)
    if (req.user.role !== 'admin' && req.user._id !== req.params.id) {
      return res.status(403).json({ message: '权限不足' });
    }
    
    // 删除用户
    const user = await User.findByIdAndDelete(req.params.id);
    if (!user) {
      return res.status(404).json({ message: '用户不存在' });
    }
    
    res.json({ message: '用户删除成功' });
  } catch (error) {
    res.status(400).json({ message: '用户删除失败', error: error.message });
  }
});

module.exports = router;

主应用文件(app.js)

const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const userRoutes = require('./routes/users');

// 加载环境变量
dotenv.config();

// 创建 Express 应用
const app = express();

// 中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 路由
app.use('/api/users', userRoutes);

// 连接 MongoDB
mongoose.connect(process.env.MONGO_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useCreateIndex: true
})
.then(() => {
  console.log('MongoDB 连接成功');
  
  // 启动服务器
  const port = process.env.PORT || 3000;
  app.listen(port, () => {
    console.log(`服务器已启动,监听端口 ${port}`);
  });
})
.catch((error) => {
  console.error('MongoDB 连接失败:', error);
});

代码解析:

  1. 项目结构:使用了模块化的项目结构,将代码分为模型、路由、中间件和工具等部分

  2. 密码处理:使用 bcryptjs 库对密码进行加密存储,确保密码安全

  3. 认证机制:使用 JWT(JSON Web Token)实现用户认证,保护需要认证的路由

  4. 权限控制:实现了基本的权限控制,区分普通用户和管理员的操作权限

  5. 数据验证:使用 Mongoose 的 Schema 定义和验证功能,确保数据的完整性

  6. 错误处理:对各种操作可能出现的错误进行了处理,返回友好的错误信息

运行方法:

  1. 安装依赖:npm install express mongoose bcryptjs jsonwebtoken dotenv
  2. 创建项目结构,将上述代码保存到对应文件
  3. 创建 .env 文件,配置 MongoDB 连接字符串和 JWT 密钥
  4. 启动 MongoDB 服务
  5. 启动应用:node app.js
  6. 使用 API 测试工具(如 Postman)测试各个接口

学习目标

通过本集的学习,你应该能够:

  1. 理解 MongoDB 的基本概念和特点
  2. 掌握使用 Mongoose ODM 连接 MongoDB 的方法
  3. 学会定义 Schema 和 Model,实现数据模型的结构和验证
  4. 掌握 CRUD 操作的实现方法
  5. 理解中间件、虚拟属性等 Mongoose 高级特性
  6. 实现一个完整的用户管理系统,包括注册、登录、查询、更新和删除操作

小结

MongoDB 是一种灵活、可扩展的 NoSQL 数据库,非常适合处理结构化程度不高或经常变化的数据。在 Node.js 应用中,使用 Mongoose ODM 可以更加方便地与 MongoDB 交互,它提供了模式定义、数据验证、中间件等功能,使得数据库操作更加直观和安全。

通过本集的学习,你已经掌握了 MongoDB 的基本概念、Mongoose 的使用方法,以及如何实现一个完整的用户管理系统。这些知识将为你开发各种基于 Node.js 和 MongoDB 的应用打下坚实的基础。

在下一集中,我们将学习 Node.js MySQL 连接,了解如何在 Node.js 应用中使用 MySQL 数据库。

« 上一篇 Node.js WebSocket 通信 下一篇 » Node.js MySQL 连接