连接数据库


我们使用 Mongolass 这个模块操作 mongodb 进行增删改查。在 myblog 下新建 lib 目录,在该目录下新建 mongo.js,添加如下代码:

lib/mongo.js

const config = require('config-lite')(__dirname)
const Mongolass = require('mongolass')
const mongolass = new Mongolass()
mongolass.connect(config.mongodb)

4.6.1 为什么使用 Mongolass

早期我使用官方的 mongodb https://www.npmjs.com/package/mongodb(也叫 node-mongodb-native)库,后来也陆续尝试使用了许多其他 mongodb 的驱动库,Mongoose : https://www.npmjs.com/package/mongoose 是比较优秀的一个,使用 Mongoose 的时间也比较长。比较这两者,各有优缺点。

node-mongodb-native:

优点:

  1. 简单。参照文档即可上手,没有 Mongoose 的 Schema 那些对新手不友好的东西。
  2. 强大。毕竟是官方库,包含了所有且最新的 api,其他大部分的库都是在这个库的基础上改造的,包括 Mongoose。
  3. 文档健全。

缺点:

  1. 起初只支持 callback,会写出以下这种代码:
    mongodb.open(function (err, db) {
    if (err) {
     return callback(err)
    }
    db.collection('users', function (err, collection) {
     if (err) {
       return callback(err)
     }
     collection.find({ name: 'xxx' }, function (err, users) {
       if (err) {
         return callback(err)
       }
     })
    ...
    

或者:

MongoClient.connect('mongodb://localhost:27017', function (err, mongodb) {
  if (err) {
    return callback(err)
  }
  mongodb.db('test').collection('users').find({ name: 'xxx' }, function (err, users) {
    if (err) {
      return callback(err)
    }
  })
  ...

现在支持 Promise 了,和 co 一起使用好很多。

  1. 不支持文档校验。Mongoose 通过 Schema 支持文档校验,虽说 mongodb 是 no schema 的,但在生产环境中使用 Schema 有两点好处。一是对文档做校验,防止非正常情况下写入错误的数据到数据库,二是可以简化一些代码,如类型为 ObjectId 的字段查询或更新时可通过对应的字符串操作,不用每次包装成 ObjectId 对象。

Mongoose:

优点:

  1. 封装了数据库的操作,给人的感觉是同步的,其实内部是异步的。如 mongoose 与 MongoDB 建立连接:
    const mongoose = require('mongoose')
    mongoose.connect('mongodb://localhost/test')
    const BlogModel = mongoose.model('Blog', { title: String, content: String })
    BlogModel.find()
    
  2. 支持 Promise。这个也无需多说,Promise 是未来趋势,可结合 co 使用,也可结合 async/await 使用。
  3. 支持文档校验。如上所述。

缺点(个人观点):

  1. 功能多,复杂。Mongoose 功能很强大,包括静态方法,实例方法,虚拟属性,hook 函数等等,混用带来的后果是逻辑复杂,代码难以维护。
  2. 较弱的 plugin 系统。如:schema.pre('save', function(next) {})schema.post('find', function(next) {}),只支持异步 next(),灵活性大打折扣。
  3. 其他:对新手来说难以理解的 Schema、Model、Entity 之间的关系;容易混淆的 toJSON 和 toObject,以及有带有虚拟属性的情况;用和不用 exec 的情况以及直接用 then 的情况;返回的结果是 Mongoose 包装后的对象,在此对象上修改结果却无效等等。

Mongolass

Mongolass 保持了与 mongodb 一样的 api,又借鉴了许多 Mongoose 的优点,同时又保持了精简。

优点:

  1. 支持 Promise。
  2. 官方一致的 api。
  3. 简单。参考 Mongolass 的 readme 即可上手,比 Mongoose 精简的多,本身代码也不多。
  4. 可选的 Schema。Mongolass 中的 Schema 是可选的,并且只用来做文档校验。如果定义了 schema 并关联到某个 model,则插入、更新和覆盖等操作都会校验文档是否满足 schema,同时 schema 也会尝试格式化该字段,类似于 Mongoose,如定义了一个字段为 ObjectId 类型,也可以用 ObjectId 的字符串无缝使用一样。如果没有 schema,则用法跟原生 mongodb 库一样。
  5. 简单却强大的插件系统。可以定义全局插件(对所有 model 生效),也可以定义某个 model 上的插件(只对该 model 生效)。Mongolass 插件的设计思路借鉴了中间件的概念(类似于 Koa),通过定义 beforeXXXafterXXX (XXX为操作符首字母大写,如:afterFind)函数实现,函数返回 yieldable 的对象即可,所以每个插件内可以做一些其他的 IO 操作。不同的插件顺序会有不同的结果,而且每个插件的输入输出都是 plain object,而非类 Mongoose 包装后的对象,没有虚拟属性,无需调用 toJSON 或 toObject。Mongolass 中的 .populate()就是一个内置的插件。
  6. 详细的错误信息。用过 Mongoose 的人一定遇到过这样的错: CastError: Cast to ObjectId failed for value "xxx" at path "_id" 只知道一个期望是 ObjectId 的字段传入了非期望的值,通常很难定位出错的代码,即使定位到也得不到错误现场。得益于 another-json-schema,使用 Mongolass 在查询或者更新时,某个字段不匹配它定义的 schema 时(还没落到 mongodb)会给出详细的错误信息,如下所示: ```js const Mongolass = require('mongolass') const mongolass = new Mongolass('mongodb://localhost:27017/test')

const User = mongolass.model('User', { name: { type: 'string' }, age: { type: 'number' } })

User .insertOne({ name: 'nswbmw', age: 'wrong age' }) .exec() .then(console.log) .catch(function (e) { console.error(e) console.error(e.stack) }) / { [Error: ($.age: "wrong age") ✖ (type: number)] validator: 'type', actual: 'wrong age', expected: { type: 'number' }, path: '$.age', schema: 'UserSchema', model: 'User', plugin: 'MongolassSchema', type: 'beforeInsertOne', args: [] } Error: ($.age: "wrong age") ✖ (type: number) at Model.insertOne (/Users/nswbmw/Desktop/mongolass-demo/node_modules/mongolass/lib/query.js:108:16) at Object. (/Users/nswbmw/Desktop/mongolass-demo/app.js:10:4) at Module._compile (module.js:409:26) at Object.Module._extensions..js (module.js:416:10) at Module.load (module.js:343:32) at Function.Module._load (module.js:300:12) at Function.Module.runMain (module.js:441:10) at startup (node.js:139:18) at node.js:974:3 / `` 可以看出,错误的原因是在 insertOne 一条用户数据到用户表的时候,age 期望是一个 number 类型的值,而我们传入的字符串wrong age`,然后从错误栈中可以快速定位到是 app.js 第 10 行代码抛出的错。

缺点:

  1. schema 功能较弱,缺少如 required、default 功能。