從零開(kāi)始的Koa實(shí)戰(zhàn)(6)插入數(shù)據(jù)

我們的系統(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 坪创、 servicecontroller 姐赡。

關(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)建 controllerservice 發(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)到了 articleControllercreate 方法纺蛆,當(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月杉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刃跛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子苛萎,更是在濱河造成了極大的恐慌桨昙,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腌歉,死亡現(xiàn)場(chǎng)離奇詭異蛙酪,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)翘盖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)桂塞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人馍驯,你說(shuō)我怎么就攤上這事阁危。” “怎么了汰瘫?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵狂打,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我混弥,道長(zhǎng)趴乡,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任剑逃,我火速辦了婚禮浙宜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛹磺。我一直安慰自己粟瞬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布萤捆。 她就那樣靜靜地躺著裙品,像睡著了一般俗批。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上市怎,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天岁忘,我揣著相機(jī)與錄音,去河邊找鬼区匠。 笑死干像,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的驰弄。 我是一名探鬼主播麻汰,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼戚篙!你這毒婦竟也來(lái)了五鲫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤岔擂,失蹤者是張志新(化名)和其女友劉穎位喂,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體乱灵,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡塑崖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了阔蛉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弃舒。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖状原,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情苗踪,我是刑警寧澤颠区,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站通铲,受9級(jí)特大地震影響毕莱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颅夺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一朋截、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吧黄,春花似錦部服、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)奉芦。三九已至,卻和暖如春剧蹂,著一層夾襖步出監(jiān)牢的瞬間声功,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工宠叼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留先巴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓冒冬,卻偏偏與公主長(zhǎng)得像伸蚯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窄驹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345