我們的系統(tǒng)需要支持瀏覽和查找數(shù)據(jù)吼蚁,或者新增和創(chuàng)建數(shù)據(jù)纱注,為了更高效地存取信息,網(wǎng)站將使用到數(shù)據(jù)庫(kù)鉴扫。
經(jīng)過(guò)前面的實(shí)戰(zhàn),我們已經(jīng)有了下面的目錄結(jié)構(gòu):
koa-blog
├── .env.example
├── .env
├── .gitignore
├── app
│ ├── middleware
│ │ └── logger.js
│ ├── router
│ │ ├── home.js
│ │ └── index.js
│ ├── util
│ │ └── log_format.js
│ └── view
│ ├── 404.html
│ └── index.html
├── app.js
├── config
│ ├── custom-environment-variables.json
│ ├── default.json
│ └── production.json
├── package.json
└── README.md
下面我們開(kāi)始使用 Mongoose 來(lái)操作 MongoDB 數(shù)據(jù)庫(kù)澈缺,并且將逐一創(chuàng)建 model 坪创、 service、controller 姐赡。
關(guān)于Mongoose
Koa 應(yīng)用支持多款數(shù)據(jù)庫(kù)莱预,從而可以執(zhí)行新建(Create)、讀瘸浴(Read)锁施、更新(Update)和刪除(Delete)操作 (CRUD) 。我們這里準(zhǔn)備使用 MongoDB 作為數(shù)據(jù)庫(kù)。
與數(shù)據(jù)庫(kù)交互的方式
- 直接使用數(shù)據(jù)庫(kù)的原生查詢語(yǔ)言(如SQL悉抵、MongoDB API )
- 使用對(duì)象數(shù)據(jù)模型(Object Data Model肩狂,簡(jiǎn)稱 ODM)或?qū)ο箨P(guān)系模型(Object Relational Model,簡(jiǎn)稱 ORM)姥饰。 ODM / ORM 能將網(wǎng)站中的數(shù)據(jù)表示為 JavaScript 對(duì)象傻谁,然后將它們映射到底層數(shù)據(jù)庫(kù)。一些 ORM 只適用某些特定數(shù)據(jù)庫(kù)列粪,還有一些是普遍適用的审磁。
了解以上這些,我們來(lái)介紹今天的主角 Mongoose 岂座,它是一款為異步工作環(huán)境設(shè)計(jì)的 MongoDB 對(duì)象建模工具态蒂。
安裝Mongoose
$ npm install mongoose --save
安裝 Mongoose 會(huì)添加 MongoDB 包括數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序在內(nèi)的所有依賴項(xiàng),再加上我們?cè)谇懊娴膶?shí)戰(zhàn)安裝并設(shè)置好了 MongoDB 费什,現(xiàn)在可以直接使用 Mongoose 連接數(shù)據(jù)庫(kù)钾恢。
連接MongoDB
可以使用 require()
引入 mongoose
,并通過(guò) mongoose.connect()
連接到本地?cái)?shù)據(jù)庫(kù)鸳址,但是為了方便管理瘩蚪,我們還是單獨(dú)建立一個(gè) plugin.js
文件,連接數(shù)據(jù)庫(kù):
// config/plugin.js
const mongoose = require('mongoose');
const config = require('config');
const dbConfig = config.get('Database');
exports.mongooseConnect = (request, response) => {
mongoose.connect(`mongodb://${dbConfig.user}:${dbConfig.password}@${dbConfig.host}:${dbConfig.port}/${dbConfig.dbName}?authSource=${dbConfig.dbName}`);
let db = mongoose.connection;
db.on('error', () => {
console.log('Mongoose連接錯(cuò)誤: ' + err);
});
db.once('open', (callback) => {
console.log(`Mongoose連接到${dbConfig.dbName}`);
});
}
然后在 app.js
引入 config/plugin.js
:
// app.js
require('dotenv-safe').config(); // 只需要引入一次
const Koa = require('koa');
const config = require('config'); // 引入config
const appConfig = config.get('App'); // 直接使用 config 獲取App的配置
const apiPrefix = config.get('Router.apiPrefix'); // 可以通過(guò)Router.apiPrefix獲取具體的值
const dbConfig = config.get('Database');
+ const { mongooseConnect } = require('./config/plugin');
+ mongooseConnect();
// ...
啟動(dòng)服務(wù) npm start
即可看到數(shù)據(jù)庫(kù)連接成功:
服務(wù)已經(jīng)啟動(dòng)稿黍,訪問(wèn):http://localhost:3001/api
Mongoose連接到koaBlog
創(chuàng)建Schema
數(shù)據(jù)庫(kù)的模型使用 Schema
接口進(jìn)行定義疹瘦,在 mongoose 中,所有的東西都從 Schema 中衍生出來(lái)巡球。 Schema
可以定義每個(gè)文檔中存儲(chǔ)的字段及字段的驗(yàn)證要求和默認(rèn)值言沐。
我們先來(lái)定義一個(gè)Schema(模式),在 app
目錄新建一個(gè)文件夾 model
辕漂,再在其中建一個(gè)文件 article.js
呢灶,準(zhǔn)備通過(guò)它創(chuàng)建文章的模式 :
// app/model/article.js
// 引入 Mongoose
const mongoose = require('mongoose');
// 定義一個(gè)模式
const Schema = mongoose.Schema;
const ArticleSchema = new Schema({
title: { // 標(biāo)題
type: String,
required: true
},
author: { type: Schema.Types.ObjectId, ref: 'User' }, // 作者
content: String, // 正文
status: { // 1 未發(fā)布 2 發(fā)布
type: Number,
default: 1
},
summary: String, // 簡(jiǎn)介
cover: String, // 封面
publishDate: Date, // 發(fā)布時(shí)間
}, {
timestamps: { // 使用時(shí)間戳
createdAt: 'createDate', // 將創(chuàng)建時(shí)間映射到createDate
updatedAt: 'updateDate' // 將修改時(shí)間映射到updateDate
}
});
// 使用populate查詢作者的信息
ArticleSchema.pre('findOne', function () {
this.populate('author', '_id name sex avatarUrl');
});
上面的代碼片段中定義了一個(gè)簡(jiǎn)單的模式。首先引入 mongoose
钉嘹,然后使用 Schema
構(gòu)造器創(chuàng)建一個(gè)新的模式實(shí)例鸯乃,使用構(gòu)造器的對(duì)象參數(shù)定義各個(gè)字段泣矛、類型以及默認(rèn)值株扛。并且使用了 mongoose 提供的 timestamps
設(shè)置了創(chuàng)建時(shí)間和修改時(shí)間,以后我們創(chuàng)建和修改文檔徘铝,這些時(shí)間會(huì)自動(dòng)變更陈辱。
創(chuàng)建Model
定義模型(model)類后奖年,可以使用它們來(lái)創(chuàng)建、更新或刪除記錄沛贪,以及通過(guò)查詢來(lái)獲取所有記錄或特定子集陋守。
使用 mongoose.model('集合別名'震贵, 模式)
方法從模式創(chuàng)建模型:
// app/model/article.js
// 引入 Mongoose
const mongoose = require('mongoose');
// 定義一個(gè)模式
const Schema = mongoose.Schema;
const ArticleSchema = new Schema({
// ...
});
// 使用populate查詢作者的信息
ArticleSchema.pre('findOne', function () {
this.populate('author', '_id name sex avatarUrl');
});
+ module.exports = mongoose.model('Article', ArticleSchema);
創(chuàng)建Service
在上面的實(shí)戰(zhàn)中,我們已經(jīng)創(chuàng)建好了模型(model)水评,接下可以通過(guò) model
實(shí)例調(diào)用 save()
來(lái)向數(shù)據(jù)庫(kù)新增文檔猩系。這些方法都是 mongoose 提供給的。
為了方便以后抽出通用的方法操作(CRUD)中燥,這里新增一個(gè)通用的 service 來(lái)對(duì)模型進(jìn)行調(diào)用寇甸。
首先新建 service
目錄,創(chuàng)建 base.js
文件疗涉,這個(gè)文件將處理一些通用的 model 調(diào)用邏輯:
// app/service/base.js
class Service {
constructor(model) {
this.model = model
}
// 創(chuàng)建記錄
create(data) {
return this.model(data).save()
}
}
module.exports = Service;
接著我們將創(chuàng)建新的 service
文件: app/service/article.js
拿霉,創(chuàng)建一個(gè) class
進(jìn)行導(dǎo)出,完整代碼如下:
// app/service/article.js
const articleModel = require('../model/article');
const Service = require('./base');
class ArticleService extends Service {
constructor() {
super(articleModel)
}
// ...
}
module.exports = new ArticleService();
當(dāng)然咱扣,還需要將 service
導(dǎo)出绽淘,以提供 controller
使用,因此在 app/service
目錄創(chuàng)建 index.js
文件偏窝,將目錄里面的 service 都導(dǎo)出來(lái):
// app/service/index.js
const article = require('./article');
module.exports = {
article
};
到這里收恢,我們的 service 創(chuàng)建完畢,接下來(lái)需要來(lái)調(diào)用 service 進(jìn)行數(shù)據(jù)操作祭往。
創(chuàng)建Controller
Controller(控制器)是應(yīng)用程序中處理用戶交互的部分,通郴鹬希控制器負(fù)責(zé)從視圖讀取數(shù)據(jù)硼补,控制用戶輸入,并向模型發(fā)送數(shù)據(jù)熏矿。接下來(lái)的實(shí)戰(zhàn)中已骇,我們將創(chuàng)建 controller
向 service
發(fā)送數(shù)據(jù)。
首先創(chuàng)建 app/controller
目錄和對(duì)應(yīng)的文件 app/controller/index.js
票编、 app/controller/article.js
:
// app/controller/article.js
const { article } = require("../service"); // 引入service
class ArticleController {
async create(ctx) {
try {
const newArticle = await article.create({
title: "第一條數(shù)據(jù)",
content: "從零開(kāi)始的koa實(shí)戰(zhàn)",
summary: "實(shí)戰(zhàn)"
});
ctx.body = newArticle;
} catch (err) {
ctx.body = err;
throw new Error(err);
}
}
}
module.exports = new ArticleController();
上面的代碼中褪储,我們先固定的創(chuàng)建一條數(shù)據(jù),title
慧域、 content
鲤竹、 summary
都是固定的值。
ArticleController
使用了 service
定義的 create
方法來(lái)向數(shù)據(jù)庫(kù)創(chuàng)建記錄昔榴,并且我們依照 ArticleSchema
定義的數(shù)據(jù)模型進(jìn)行傳參辛藻。接著,將 ArticleController
導(dǎo)出:
// app/controller/index.js
const article = require('./article');
module.exports = {
article,
};
修改路由
前面已經(jīng)將新增數(shù)據(jù)的處理邏輯對(duì)應(yīng)到了 controller互订,接下來(lái)我們修改 routes/index.js
的路由設(shè)置吱肌,讓請(qǐng)求能夠指定到對(duì)應(yīng)的 controller 上。在下面的代碼中仰禽,我們暫時(shí)使用 get 類型來(lái)做測(cè)試氮墨,但是建議遵循RESTful風(fēng)格來(lái)進(jìn)行設(shè)計(jì):
// app/router/index.js
const Router = require('koa-router');
const config = require('config'); // 引入config
const apiPrefix = config.get('Router.apiPrefix');
const router = new Router();
router.prefix(apiPrefix); // 設(shè)置路由前綴
const home = require('./home');
+ const { article } = require('../controller'); // 引入controller
const index = async (ctx, next) => {
await ctx.render('index', {title: 'Index', link: 'home'});
};
router.get('/', index);
router.get('/index', index);
+ router.get('/article', article.create);
router.use('/home', home.routes(), home.allowedMethods()); // 設(shè)置home的路由
module.exports = router;
我們將路由的 /article
對(duì)應(yīng)到了 articleController
的 create
方法纺蛆,當(dāng)重啟服務(wù)之后,在瀏覽器訪問(wèn): http://localhost:3000/api/article 规揪,可以看到已經(jīng)創(chuàng)建了一條數(shù)據(jù)犹撒。頁(yè)面中顯示的數(shù)據(jù)如:
{"status":1,"_id":"5ef204b5b40da108dc47ae30","title":"第一條數(shù)據(jù)","content":"從零開(kāi)始的koa實(shí)戰(zhàn)","summary":"實(shí)戰(zhàn)","createDate":"2020-06-23T13:33:41.858Z","updateDate":"2020-06-23T13:33:41.858Z","__v":0}
到這里,我們已經(jīng)能夠通過(guò)請(qǐng)求對(duì)MongoDB數(shù)據(jù)庫(kù)進(jìn)行數(shù)據(jù)插入粒褒,以后還將繼續(xù)完善各項(xiàng)邏輯识颊。
參考資料:
https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Express_Nodejs/mongoose 。
下一步奕坟,我們來(lái)以RESTful風(fēng)格設(shè)計(jì)一個(gè)接口祥款,使用postman來(lái)調(diào)用,從而提供用戶注冊(cè)API月杉。