Skip to content

11.6 mongoose 操作数据库

了解了 MongoDB 的基本概念以及用法后,我们就要在 Express 中接入并使用了。为了更优雅地在 Node.js 环境下操作 MongoDB,我们还需要一个好用的第三方包 mongoose,首先安装:

sh
$ yarn add mongoose

安装后,在项目中使用 mongoose 提供的 API 来实现一系列数据库相关的操作。

11.6.1 连接数据库

假设已有一台可用的 MongoDB 数据库服务,第一步是创建一个名为 juejin_blogs 数据库,并创建连接该数据库的用户名和密码,信息如下:

  • 数据库地址(IP+端口):127.0.0.1:11027。
  • 数据库名称:juejin_blogs。
  • 数据库用户名:ruidoc。
  • 数据库密码:qwertyu123456。

注意:创建数据库用户时需要指定用户角色,角色必须是 “dbOwner”,否则后面可能会遇到权限不够的问题。

安装后在项目下创建一个新文件 config/mongo.js,然后编写一个连接数据库的中间件,代码如下:

js
// config/mongo.js
const mongoose = require("mongoose");
const connect = (req, res, next) => {
  mongoose
    .connect("mongodb://127.0.0.1:11027/juejin_blogs", {
      user: "ruidoc",
      pass: "qwertyu123456",
    })
    .then(() => {
      console.log("数据库连接成功");
      next();
    })
    .catch((err) => {
      console.log("数据库连接失败:", err);
      res.status(500).send({
        message: "数据库连接失败",
      });
    });
};

module.exports = connect;

代码中的 mongodb://127.0.0.1:11027/juejin_blogs 就是数据库的连接地址,它由固定的 “mongodb://” 协议 + IP 地址 + 端口 + 数据库名称组成。

当连接成功后,进入下一个中间件;如果连接失败,直接响应 500 错误。

完成之后在入口文件 index.js 中加载这个中间件。

js
const mongoInit = require("./config/mongo");
app.use(mongoInit);

此时重新启动程序,控制台会打印出“数据库连接成功”。

11.6.2 规范文档结构

数据库连接之后,创建集合和文档完全由程序控制,MongoDB 不会限制文档的格式。这种高度的灵活性既有好处,也有弊端,弊端就是我们可能会添加不规范的数据。

为了规范文档的数据格式,mongoose 提供了一个 Schema 的概念,作用是提前设定某个集合中的文档格式(文档有哪些字段以及对字段的约束),类似用 TypeScript 定义 interface 一样。

还是以一个存储日志的集合为例,在创建该集合前,首先用 Schema 定义一个文档结构,方法如下:

js
const mongoose = require('mongoose')

const logsSchema = new mongoose.Schema({
  title: String,
  content: {
    type: String,
    required: true
  }
  date: {
    type: Date,
    default: Date.now
  },
})

上述 Schema 中定义的文档共有三个字段,每个字段的含义以及约束条件如下:

  • title:日志标题,类型是字符串。
  • content:日志内容,类型是字符串,必填。
  • date:创建时间,默认为当前时间。

字段的值可以直接是类型(如:String),或者是一个对象,对象中包含了多个约束条件,Schema 支持的常用的约束条件及含义如下:

  • type:字段类型,包括 String、Number、Date、Boolean、Array 等常见类型。
  • required:字段是否必填,值为 true 或 false。
  • default:字段默认值,设置后字段会自动创建。
  • unique:字段值是否唯一,设置后多个文档的字段值不能重复。
  • enum:给定一个数组,字段值必须是数组中的某一个。
  • validate:函数,自定义验证字段是否满足要求。

现在创建一个日志集合的 Model 模型(mongoose 提供的用于操作集合的类),指定集合名称为 “logs”,并使用该 Schema 约束集合,方法如下:

js
const LogsModel = mongoose.model("logs", logsSchema);

接下来我们就可以使用 LogsModel 来操作 logs 集合了。当在集合中添加文档时,Schema 会验证传入的数据是否符合约束条件,不符合则拒绝添加,这样就能保证集合中数据的可靠性。

11.6.3 操作文档

文档的增查改删都基于上一步创建的 Model 模型来实现。模型提供了与 MongoDB 一样的数据操作方法,并且有一些优化的快捷方法可供使用。下面使用 LogsModel 来操作 logs 集合。

首先看如何使用 LogsModel 插入文档:

js
const insert = async (data) => {
  let res = await LogsModel.create(data);
  console.log(res); // 返回插入后的文档
};

MongoDB 的插入文档方法是 insertOne() 和 insertMany(),mongoose 将这两个方法合并为一个 model.create() 方法,同时支持单文档插入和批量插入。

查询文档还是使用 find() 和 findOne() 方法不变,如下:

js
const getLogs = async () => {
  let res = await LogsModel.find({
    _id: ObjectId("507f191e810c19729de860ea"),
  });
  return res;
};

注意:mongoose 操作中遇到 _id 字段也要用 ObjectId() 方法包裹,但是 Node.js 中并没有提供这个方法,所以我们要自己实现并定义在全局对象 global 下:

js
const { Types } = require("mongoose");
global.ObjectId = (id) => new Types.ObjectId(id);

聚合查询完全按照 MongoDB 的方法执行即可,两者没有区别。

更新操作的方法名也没有变化,区别是可以不用 $set 操作符直接更新数据,代码如下:

js
const updateLogs = async () => {
  let res = await LogsModel.updateMany(
    {
      _id: ObjectId("507f191e810c19729de860ea"),
    },
    {
      content: "用户使用支付宝登录",
    }
  );
};

对于上面根据 ID 查到文档并更新的场景,mongoose 提供了一个快捷方法,其参数 ID 不需要用 ObjectId() 包裹,更方便一写,如下:

js
let id = "507f191e810c19729de860ea";
let res = await LogsModel.findByIdAndUpdate(id, {
  content: "用户使用支付宝登录",
});

同理,对于根据 ID 删除对应文档,mongoose 也提供了一个快捷方法:

js
let id = "507f191e810c19729de860ea";
let res = await LogsModel.findByIdAndDelete(id);
if (res) {
  console.log("删除成功");
}

常用的方法就是这些,了解了基本的增查改删操作,我们就可以进入业务接口开发的环节。

本章小结

本章主要介绍了 API 开发的基础知识。本章首先说明了为什么要使用 Serverless 云开发,然后注册并开通了阿里云的函数计算,编写了第一个云函数。在创建云函数并选择自定义运行时的时候,可以将 Express 框架作为云函数的基础代码。

本章还基于 Express 框架介绍了 API 开发的基础知识(这部分内容是本章的重点),主要包括 Express 框架的使用和 MongoDB 的操作。只有了解了这些基础知识才能进入后面的业务开发环节。