# 使用 Mongoose

URL: https://caijiao.org/express/chapter6/2-mongoose
Source: docs/express/chapter6/2-mongoose.md
Description: 本章节介绍如何在 Express 应用中使用 Mongoose，讲解如何定义模型、进行 CRUD 操作以及进行数据验证。

Mongoose 是一个为 Node.js 提供 MongoDB 的对象数据建模（ODM）库。它通过为 MongoDB 提供 schema 和模型的支持，使得开发人员能够以更加结构化和简洁的方式与数据库进行交互。本章节将详细介绍如何在 Express 中使用 Mongoose，包括如何定义模型、进行 CRUD 操作、以及使用数据验证。

## 安装 Mongoose

首先，确保已经安装了 Mongoose：

```bash
npm install mongoose
````

## 连接到 MongoDB

在开始使用 Mongoose 之前，我们需要连接到 MongoDB 数据库。连接方法与 MongoDB 原生驱动类似，但 Mongoose 提供了更丰富的功能和更简洁的 API。

```js
const mongoose = require('mongoose');

// 连接 MongoDB 数据库
mongoose.connect('mongodb://localhost:27017/expressTutorial', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
  .then(() => console.log('MongoDB 连接成功'))
  .catch((err) => console.error('MongoDB 连接失败', err));
```

## 定义 Mongoose Schema

Schema 是定义文档结构的蓝图，它定义了 MongoDB 文档中的字段及其类型。每个字段可以设置默认值、验证规则、以及其他选项。

### 1. 创建用户模型

下面是一个简单的用户模型，包含用户名、电子邮件和密码等字段。

```js
const mongoose = require('mongoose');

// 定义用户 Schema
const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    unique: true, // 保证用户名唯一
    trim: true, // 删除用户名首尾的空白字符
  },
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true, // 转换为小写字母
    match: [/^\S+@\S+\.\S+$/, '请提供有效的电子邮件地址'], // 邮箱格式验证
  },
  password: {
    type: String,
    required: true,
    minlength: 6, // 密码长度至少 6 个字符
  },
  createdAt: {
    type: Date,
    default: Date.now, // 默认值为当前时间
  },
});

// 创建用户模型
const User = mongoose.model('User', userSchema);
```

### 2. 创建其他模型

类似地，你可以为其他实体（如文章、评论等）创建不同的 Schema 和模型。例如，一个简单的博客文章模型：

```js
const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
  },
  content: {
    type: String,
    required: true,
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User', // 引用用户模型
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

const Post = mongoose.model('Post', postSchema);
```

## CRUD 操作

在 MongoDB 中，常见的 CRUD（创建、读取、更新、删除）操作可以通过 Mongoose 的 API 来完成。下面介绍如何使用 Mongoose 进行这些操作。

### 1. 创建数据（Create）

创建数据非常简单，只需要使用模型的 `save` 方法。

```js
// 创建一个新用户
const newUser = new User({
  username: 'john_doe',
  email: 'john.doe@example.com',
  password: 'securePassword123',
});

newUser.save()
  .then(() => console.log('用户创建成功'))
  .catch((err) => console.error('用户创建失败', err));
```

### 2. 查询数据（Read）

#### 2.1 查询所有用户

使用 `find` 方法查询所有符合条件的文档。

```js
User.find()
  .then((users) => console.log('所有用户：', users))
  .catch((err) => console.error('查询失败', err));
```

#### 2.2 查询单个用户

使用 `findOne` 方法查询单个用户。

```js
User.findOne({ username: 'john_doe' })
  .then((user) => console.log('查询到的用户：', user))
  .catch((err) => console.error('查询失败', err));
```

#### 2.3 条件查询

你可以通过条件查询过滤数据。例如，查询电子邮件为 `john.doe@example.com` 的用户。

```js
User.find({ email: 'john.doe@example.com' })
  .then((users) => console.log('查询到的用户：', users))
  .catch((err) => console.error('查询失败', err));
```

#### 2.4 查询并返回特定字段

如果你只需要某些字段（如用户名和电子邮件），可以在查询时指定返回的字段。

```js
User.find({ email: 'john.doe@example.com' }, 'username email')
  .then((users) => console.log('查询到的用户：', users))
  .catch((err) => console.error('查询失败', err));
```

### 3. 更新数据（Update）

#### 3.1 更新单个字段

使用 `updateOne` 或 `updateMany` 方法来更新数据。例如，更新用户的密码：

```js
User.updateOne({ username: 'john_doe' }, { password: 'newPassword123' })
  .then(() => console.log('用户密码更新成功'))
  .catch((err) => console.error('更新失败', err));
```

#### 3.2 更新并返回修改后的数据

如果你希望在更新数据后返回更新后的文档，可以设置 `new: true` 选项。

```js
User.findOneAndUpdate(
  { username: 'john_doe' },
  { password: 'updatedPassword456' },
  { new: true }
)
  .then((updatedUser) => console.log('更新后的用户：', updatedUser))
  .catch((err) => console.error('更新失败', err));
```

### 4. 删除数据（Delete）

#### 4.1 删除单个文档

```js
User.deleteOne({ username: 'john_doe' })
  .then(() => console.log('用户删除成功'))
  .catch((err) => console.error('删除失败', err));
```

#### 4.2 删除多个文档

```js
User.deleteMany({ email: /example.com$/ }) // 删除所有邮箱为 example.com 的用户
  .then(() => console.log('删除用户成功'))
  .catch((err) => console.error('删除失败', err));
```

## 数据验证

Mongoose 提供了内置的验证功能，允许我们对文档字段进行校验。例如，在前面定义的 `User` 模型中，我们为电子邮件和密码字段添加了验证规则。

#### 1. 自定义验证

除了内置的验证器外，我们还可以添加自定义验证逻辑。比如，验证用户名的长度：

```js
userSchema.path('username').validate(function(value) {
  return value.length >= 3; // 用户名至少为 3 个字符
}, '用户名长度必须大于或等于 3 个字符');
```

## 关联关系

Mongoose 支持建立模型之间的关系。例如，文章（Post）和用户（User）之间的关系。可以使用 `ref` 字段建立引用关系。

#### 1. 关联查询

```js
Post.find()
  .populate('author')  // 填充作者字段，查询到用户的信息
  .then((posts) => console.log(posts))
  .catch((err) => console.error('查询失败', err));
```

## 总结

Mongoose 为 MongoDB 提供了一个功能强大的对象数据建模工具，能够简化与数据库的交互。通过定义 Schema 和模型，使用内置的验证、查询、更新和删除方法，我们能够方便地操作数据库并保证数据的完整性。Mongoose 的功能不仅仅限于基本的 CRUD 操作，还支持数据验证、关系映射和复杂的查询操作。
