原生自然有原生的好处,但是,Mongo 自身某些特性不太适合开发,比如没有统一的
表
来约束 documents, 这会造成很多问题。Mongoose 在原生的基础上进行了拓展,很大程度上弥补了这些不足。
本篇我将在某个名为
db
的文件夹完成如下模块来完成对数据库的抽象:- db-config.js
- connect.js
- schemas.js
- models.js
- index.js
# db-config.js ↵
这是个配置文件:
00// db-config.js 01module.exports = { 02 mongoUri: 'mongodb://127.0.0.1/nc' 03}
待会会用到。
# connect.js ↵
该文件主要完成利用 mongoose 连接到数据库并取得一个数据库连接。
00// connect.js 01const mongoose = require('mongoose') 02 , { mongoUri } = require('./db-config') 03 04// Connect 05mongoose.connect(mongoUri); 06 07module.exports = mongoose;
# schemas.js ↵
Mongoose 里的 Schema 即模式,描述了一个 Collection 的结构,是整个 Mongoose 大厦的基石。
schemas.js 里写了各种 Collection 的 schema,如果你对所谓
模式
还不是很清楚,我想你只要看了如下代码,就有一个莫名的认知了:00// schemas.js 01const mongoose = require('./connect'); 02 03let schemas = {}; 04 05schemas.user = mongoose.Schema({ 06 name: { 07 type: String, 08 required: true 09 }, 10 avatar: { 11 type: String, 12 required: true 13 }, 14 bg: { 15 type: String, 16 default: 'bg no set' 17 }, 18 created_at: { 19 type: Date, 20 default: Date.now 21 } 22}); 23 24module.exports = schemas;
上面可以看到
schemes.user
是一个 schema,上面有 name
avatar
bg
created_at
这些,实际上这些就是 schemes.user
的一个实例应该具备的 字段
也就是一个
document
里应该有的字段, Mongo 原生很宽松,什么东西都可以往 Collection 里填,而实际情况下,并不是什么东西都可以扔进里面的,尤其是处理用户 POST 过来的表单时更为重要,万一有人不怀好心发各种数据污染数据库就很让人不爽了为此,就要在 HTTP 路由里面写上很多校验值是否合法的逻辑,这样反而增加了好多有的没的工作量 …
关于数据的合法性问题我还写过两篇博客讨论这个问题,学习了 Mongoose 之后,我发现自己造的轮子远没有 Mongoose 的类型检查优雅(虽然想法很近似233 都是有一个 schemas 来描述数据格式)。
关于如何使用 Mongoose 的类型检查,将会在文末讲述,现在这部分是完成
db
包.# models.js ↵
MongoDB 的结构如此简单以至于人人都能理解:
- 一个 MongoDB 数据库里面有很多 Collections
- 一个 Collection 里面有很多 Documents
schema 描述了一个 Collection 里所有 Documents 应该遵从的法则,而 Model 实际上就是这个 Collection 、及其上面所有运算的一个系统 …
这不是代数系统吗?!!
00// models.js 01// Schemas Map To Models 02const schemas = require('./schemas') 03 , mongoose = require('./connect'); 04 05let schemasNames = Object.keys(schemas); 06 07let models = schemasNames.reduce((acc, name) => { 08 acc[name + 'Model'] = mongoose.model( 09 name, 10 schemas[name] 11 ); 12 13 return acc; 14}, {}) 15 16module.exports = models;
经过上述代码处理后 models 里面是这样:
00{ 01 userModel: 一个由schemas.user得到的model 02}
# index.js ↵
index.js 什么事也没做 仅仅是 models.js 的代理
00// index.js 01const models = require('./models'); 02 03module.exports = models;
至此我们的 db 包完成了。 可以如下方式引用:
00const db = require('./db');
# 查询 ↵
00const { userModel } = require('./db'); 01 02userModel.find().then(docs => { 03 // docs 是一个数组,即 documents 代表数据实例 04 console.log(docs); 05}, err => { 06 // 查询错误 07 console.log(err); 08});
# 插入 ↵
Mongoose 的插入极有意思,利用 model 来完成数据实例化:
先来看看关于 user 的 schema :
00name: { 01 type: String, 02 required: true 03}, 04avatar: { 05 type: String, 06 required: true 07}, 08bg: { 09 type: String, 10 default: 'bg no set' 11}, 12created_at: { 13 type: Date, 14 default: Date.now 15}
- required 表示是否必填
- type 表示类型
- default 表示默认值,可以是函数,需要时候会被调用 比如 created_at
除此之外还有很多这些字段,都可以在官方文档上找到。
回到正事,插入一个数据:
00const { userModel } = require('./db'); 01 02let data = new userModel({ 03 name: 'varEczn', 04 avatar: 'xxxx.jpg' 05}); 06 07data.save().then(suc => { 08 console.log('保存成功'); 09}, err => { 10 console.log('失败'); 11})
如果数据校验出错,则 save() 返回的 Promise 将会转为
Rejected
而不是 Resolved
而 err 里面将会写上错误的原因,包括类型校验的错误。# 更新 ↵
更新也很有意思:
00let id = 'xxx'; 01userModel.findOne({ 02 id: id 03}).then(doc => { 04 if (doc){ 05 doc.set({ 06 name: '新名字' 07 }); 08 09 doc.save().then(suc => { 10 console.log('更新成功') 11 }, err => { 12 console.log('校验错误'); 13 }) 14 } else { 15 console.log(`${id} Not Found`) 16 } 17})
# 删除 ↵
或许你也猜到了,的确如此:
00let id = 'xxx'; 01userModel.remove({ 02 id: 'xxx' 03}).then(db => { 04 console.log('删除成功:', db.result); 05}, err => { 06 console.log('删除失败,原因:', err); 07});
# More ↵
利用 Schema 可以构造出 Model, Model 是对原生查询里面 Colletion 的一个抽象,它看起来很像一个代数系统。
使用的时候也非常舒服:
00model.find({ 01 id: 'xxx' 02}).then(docs => { 03 // do sth 04})
其他的操作也都支持 promise 用起来很爽。
此外,Mongoose 封装了一些很好的方法 比如
FindOneAndRemove
或 FindOneAndUpdate
解决了好多痛点