从零开始的Koa实战(6)插入数据

我们的系统需要支持浏览和查找数据,或者新增和创建数据,为了更高效地存取信息,网站将使用到数据库。

经过前面的实战,我们已经有了下面的目录结构:

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

下面我们开始使用 Mongoose 来操作 MongoDB 数据库,并且将逐一创建 modelservicecontroller

关于Mongoose

Koa 应用支持多款数据库,从而可以执行新建(Create)、读取(Read)、更新(Update)和删除(Delete)操作 (CRUD) 。我们这里准备使用 MongoDB 作为数据库。

与数据库交互的方式

  • 直接使用数据库的原生查询语言(如SQL、MongoDB API )
  • 使用对象数据模型(Object Data Model,简称 ODM)或对象关系模型(Object Relational Model,简称 ORM)。 ODM / ORM 能将网站中的数据表示为 JavaScript 对象,然后将它们映射到底层数据库。一些 ORM 只适用某些特定数据库,还有一些是普遍适用的。

了解以上这些,我们来介绍今天的主角 Mongoose ,它是一款为异步工作环境设计的 MongoDB 对象建模工具。

安装Mongoose

$ npm install mongoose --save

安装 Mongoose 会添加 MongoDB 包括数据库驱动程序在内的所有依赖项,再加上我们在前面的实战安装并设置好了 MongoDB ,现在可以直接使用 Mongoose 连接数据库。

连接MongoDB

可以使用 require() 引入 mongoose ,并通过 mongoose.connect() 连接到本地数据库,但是为了方便管理,我们还是单独建立一个 plugin.js 文件,连接数据库:

// 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连接错误: ' + 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'); // 可以通过Router.apiPrefix获取具体的值
const dbConfig = config.get('Database');
+ const { mongooseConnect } = require('./config/plugin');
+ mongooseConnect();
// ...

启动服务 npm start 即可看到数据库连接成功:

服务已经启动,访问:http://localhost:3001/api
Mongoose连接到koaBlog

创建Schema

数据库的模型使用 Schema 接口进行定义,在 mongoose 中,所有的东西都从 Schema 中衍生出来。 Schema 可以定义每个文档中存储的字段及字段的验证要求和默认值。

我们先来定义一个Schema(模式),在 app 目录新建一个文件夹 model ,再在其中建一个文件 article.js ,准备通过它创建文章的模式 :

// app/model/article.js

// 引入 Mongoose
const mongoose = require('mongoose');

// 定义一个模式
const Schema = mongoose.Schema;

const ArticleSchema = new Schema({
    title: { // 标题
        type: String,
        required: true
    },
    author: { type: Schema.Types.ObjectId, ref: 'User' }, // 作者
    content: String, // 正文
    status: { // 1 未发布 2 发布
        type: Number,
        default: 1
    },
    summary: String, // 简介
    cover: String, // 封面
    publishDate: Date, // 发布时间
}, {
    timestamps: { // 使用时间戳
        createdAt: 'createDate', // 将创建时间映射到createDate
        updatedAt: 'updateDate' // 将修改时间映射到updateDate
    }
});

// 使用populate查询作者的信息
ArticleSchema.pre('findOne', function () {
    this.populate('author', '_id name sex avatarUrl');
});

上面的代码片段中定义了一个简单的模式。首先引入 mongoose ,然后使用 Schema 构造器创建一个新的模式实例,使用构造器的对象参数定义各个字段类型以及默认值。并且使用了 mongoose 提供的 timestamps 设置了创建时间修改时间,以后我们创建和修改文档,这些时间会自动变更。

创建Model

定义模型(model)类后,可以使用它们来创建、更新或删除记录,以及通过查询来获取所有记录或特定子集。

使用 mongoose.model('集合别名', 模式) 方法从模式创建模型:

// app/model/article.js

// 引入 Mongoose
const mongoose = require('mongoose');

// 定义一个模式
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);

创建Service

在上面的实战中,我们已经创建好了模型(model),接下可以通过 model 实例调用 save() 来向数据库新增文档。这些方法都是 mongoose 提供给的。

为了方便以后抽出通用的方法操作(CRUD),这里新增一个通用的 service 来对模型进行调用。

首先新建 service 目录,创建 base.js 文件,这个文件将处理一些通用的 model 调用逻辑:

// app/service/base.js

class Service {
    constructor(model) {
        this.model = model
    }

    // 创建记录
    create(data) {
        return this.model(data).save()
    }
}

module.exports = Service;

接着我们将创建新的 service 文件: app/service/article.js ,创建一个 class 进行导出,完整代码如下:

// app/service/article.js

const articleModel = require('../model/article');
const Service = require('./base');

class ArticleService extends Service {
    constructor() {
        super(articleModel)
    }
    // ...
}

module.exports = new ArticleService();

当然,还需要将 service 导出,以提供 controller 使用,因此在 app/service 目录创建 index.js 文件,将目录里面的 service 都导出来:

// app/service/index.js

const article = require('./article');

module.exports = {
    article
};

到这里,我们的 service 创建完毕,接下来需要来调用 service 进行数据操作。

创建Controller

Controller(控制器)是应用程序中处理用户交互的部分,通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。接下来的实战中,我们将创建 controllerservice 发送数据。

首先创建 app/controller 目录和对应的文件 app/controller/index.jsapp/controller/article.js

// app/controller/article.js

const { article } = require("../service"); // 引入service

class ArticleController {
  async create(ctx) {
    try {
      const newArticle = await article.create({
        title: "第一条数据",
        content: "从零开始的koa实战",
        summary: "实战"
      });
      ctx.body = newArticle;
    } catch (err) {
      ctx.body = err;
      throw new Error(err);
    }
  }
}

module.exports = new ArticleController();

上面的代码中,我们先固定的创建一条数据,titlecontentsummary 都是固定的值。

ArticleController 使用了 service 定义的 create 方法来向数据库创建记录,并且我们依照 ArticleSchema 定义的数据模型进行传参。接着,将 ArticleController 导出:

// app/controller/index.js

const article = require('./article');

module.exports = {
  article,
};

修改路由

前面已经将新增数据的处理逻辑对应到了 controller,接下来我们修改 routes/index.js 的路由设置,让请求能够指定到对应的 controller 上。在下面的代码中,我们暂时使用 get 类型来做测试,但是建议遵循RESTful风格来进行设计

// 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); // 设置路由前缀
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()); // 设置home的路由

module.exports = router;

我们将路由的 /article 对应到了 articleControllercreate 方法,当重启服务之后,在浏览器访问: http://localhost:3000/api/article ,可以看到已经创建了一条数据。页面中显示的数据如:

{"status":1,"_id":"5ef204b5b40da108dc47ae30","title":"第一条数据","content":"从零开始的koa实战","summary":"实战","createDate":"2020-06-23T13:33:41.858Z","updateDate":"2020-06-23T13:33:41.858Z","__v":0}

到这里,我们已经能够通过请求对MongoDB数据库进行数据插入,以后还将继续完善各项逻辑。

参考资料:

https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Express_Nodejs/mongoose

下一步,我们来以RESTful风格设计一个接口,使用postman来调用,从而提供用户注册API。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349