九、添加服务器端数据库

我们在前一章添加了 Koa 作为服务器端框架的 NUXT 应用,并提供了一些虚拟数据。在本章中,我们将设置 MongoDB 作为服务器端数据库,以替换虚拟数据。我们将编写一些 MongoDB CRUD 查询,将数据添加到数据库中,并使用asyncData从数据库中获取数据。

我们将在本章中介绍的主题如下:

  • 介绍 MongoDB
  • 编写基本 MongoDB 查询
  • 编写 MongoDB CRUD 操作
  • 使用 MongoDB CRUD 查询注入数据
  • 集成 MangGDB 与膝关节炎
  • 与 Nuxt 页面集成

介绍 MongoDB

MongoDB 是一个开源的面向文档的数据库管理系统(DBMS),它将数据存储在类似 JSON 的文档中,称为二进制 JSON(BSON)——它是 MongoDB 类似 JSON 的文档的二进制表示,可以比普通 JSON 更快地解析。它是自 2009 年以来最流行的 NoSQL 数据库系统之一,与关系数据库管理系统(RDBMSes)相比,它不使用表和行。MongoDB 中的每个数据记录都是由名称-值对(或字段-值对)组成的文档,这些名称-值对类似于 JSON 对象,但经过二进制编码以支持 JSON 范围之外的数据类型,例如 ObjectId、Date 和 binary 数据(https://docs.mongodb.com/manual/reference/bson-types/ )。因此,它被称为二进制 JSON。例如,{"hello":"world"}的文档将存储在.bson文件中,如下所示:

1600 0000 0268 656c 6c6f 0006 0000 0077
 6f72 6c64 0000

实际上,BSON 中的编码数据不是人类可读的,但在使用 MongoDB 时,我们不必太担心,因为 MongoDB 驱动程序将为您对它们进行开箱即用的编码和解码。在为 BSON 存储构建文档时,您只需要将熟悉的 MongoDB 语法、方法、操作和选择器与 JSON 文档一起使用。让我们安装 MongoDB 并开始编写。

安装 MongoDB

根据版本(社区版或企业版)和平台(Windows、Ubuntu 或 macOS),有几种安装 MongoDB 的方法。您可以点击此处提供的链接:

在 Ubuntu 20.04 上安装

在本书中,我们将在 Ubuntu 20.04(Focal Fossa)上安装 MongoDB 4.2(社区版)。如果你使用的是 Ubuntu19.10(EoanErmine),它的工作原理也是一样的。如果您正在使用其他较旧版本的 Ubuntu,如 14.04 LTS(Trusty Tahr)、16.04 LTS(Xenial Xerus)或 18.04 LTS(Bionic Beaver),请遵循上一节中 Ubuntu 链接上的 MongoDB 社区版。那么,让我们开始:

  1. mongodb.org导入公钥:
$ wget -qO - https://www.mongodb.org/static/pgp/server-4.2.asc | sudo apt-key add -

你应该在回复中得到一个OK

  1. 为 MongoDB 创建列表文件:
$ echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.2.list
  1. 更新系统中的所有本地包:
$ sudo apt-get update
  1. 安装 MongoDB 软件包:
$ sudo apt-get install -y mongodb-org

启动 MongoDB

一旦安装了 MongoDB 软件包,接下来要做的就是查看是否可以从终端启动并连接 MongoDB 服务器。那么,让我们开始:

  1. 使用以下命令在引导时手动或自动启动 MongoDB:
$ sudo systemctl start mongod
$ sudo systemctl enable mongod
  1. 通过检查其版本进行验证:
$ mongo --version

您应该在终端上获得与此类似的输出:

MongoDB shell version v4.2.1
git version: edf6d45851c0b9ee15548f0f847df141764a317e
OpenSSL version: OpenSSL 1.1.1d 10 Sep 2019
allocator: tcmalloc
modules: none
build environment:
    distmod: ubuntu1804
    distarch: x86_64
    target_arch: x86_64
  1. (可选)使用以下命令检查 MongoDB 服务器状态:
$ sudo service mongod status

您应该在终端上获得与此类似的输出:

● mongod.service - MongoDB Database Server
   Loaded: loaded (/lib/systemd/system/mongod.service; enabled;
     vendor preset: enabled)
   Active: active (running) since Fri 2019-08-30 03:37:15 UTC;
     29s ago
     Docs: https://docs.mongodb.org/manual
 Main PID: 31961 (mongod)
   Memory: 68.2M
   CGroup: /system.slice/mongod.service
           └─31961 /usr/bin/mongod --config /etc/mongod.conf
  1. 或者,使用netstat命令检查端口 27017 上是否已启动 MongoDB:
$ sudo netstat -plntu

您应该看到以下类似的输出:

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:27017 0.0.0.0: LISTEN 792/mongod
  1. 连接到 MongoDB 外壳:
$ mongo
  1. 退出 MongoDB Shell(如果需要):
> exit

如果出于任何原因,希望从系统中完全删除 MongoDB,请使用以下命令:

$ sudo apt-get purge mongodb-org*

在下一节中,您将从刚刚学习的 MongoDB Shell 开始编写一些基本查询。让我们开始吧。

编写基本 MongoDB 查询

在编写 MongoDB 查询并注入一些数据之前,首先必须连接到 MongoDB,因此打开终端并键入以下内容:

$ mongo

然后,您可以列出 MongoDB 系统中的数据库:

> show dbs

您应该获得以下输出:

admin 0.000GB
config 0.000GB

这两个数据库(adminconfig是 MongoDB 的默认数据库。然而,我们应该根据我们的需要和目的创建新的数据库。

创建数据库

登录 MongoDB shell 后,可以使用use命令在 MongoDB 中创建一个新的数据库:

> use nuxt-app

您应该得到以下结果:

switched to db nuxt-app

但是,请注意,当您要选择现有数据库时,情况是相同的:

> use admin

您应该得到以下结果:

switched to db admin

如果要删除数据库,首先使用use命令选择数据库,例如use nuxt-app,然后使用dropDatabase功能:

> db.dropDatabase()

您应该得到以下结果:

{ "dropped" : "nuxt-app", "ok" : 1 }

接下来我们将学习如何创建集合或将集合添加到我们创建的数据库中。

创建新集合

什么是 MongoDB 集合?如果您熟悉 RDBMS,那么集合类似于 RDBMS 表,它可以由不同的字段组成,模式的实施除外。我们使用createCollection方法创建以下格式的集合:

> db.createCollection(<name>, <options>)

<name>参数是集合的名称,例如用户、文章或其他名称。<options>参数是可选的,用于指定用于创建固定大小集合或验证更新和插入的集合的字段。有关这些选项的更多信息,请访问https://docs.mongodb.com/manual/reference/method/db.createCollection/ 。让我们创建一个文档,看看您还可以在以下步骤中对该文档执行哪些操作:

  1. 创建不带任何选项的集合:
> db.createCollection("users", {})

您应该得到以下结果:

{ "ok" : 1 }
  1. 使用getCollectionNames方法列出数据库中的所有集合:
> db.getCollectionNames()

您应该得到以下结果:

[ "users" ]
  1. 使用drop方法放弃users采集:
> db.users.drop()

您应该得到以下结果:

true

现在我们知道了如何创建集合,接下来应该知道的是如何向集合中添加文档。让我们在下一节讨论它。

编写 MongoDB CRUD 操作

当涉及到管理和操作数据库系统中的数据时,我们必须创建、读取、更新和删除(CRUD)文档。我们可以使用 MongoDB 的积垢操作来实现这一点。有关 MongoDB CRUD 操作的更多信息,请访问https://docs.mongodb.com/manual/crud/ 。在本书中,我们将只看到一个简单的示例,说明如何使用以下各项:

*创建操作:我们可以使用以下方法创建或向集合插入新文档:

db.<collection>.insertOne(<document>)
db.<collection>.insertMany([<document>, <document>, <document>, ...])

请注意,如果数据库中不存在该集合,这些insert操作将自动为您创建该集合。

  • 读取操作 我们可以使用以下方法从集合中获取文档:
db.<collection>.find(<query>, <projection>)
  • 更新操作:我们可以使用以下方法来修改集合中的现有文档:
db.<collection>.updateOne(<filter>, <update>, <options>)
db.<collection>.updateMany(<filter>, <update>, <options>)
db.<collection>.replaceOne(<filter>, <replacement>, <options>)
  • 删除操作:我们可以使用以下方法从集合中删除文档:
db.<collection>.deleteOne(<filter>, <options>)
db.<collection>.deleteMany(<filter>, <options>)

通过这些简化的 CRUD 操作,您可以在下一节中开始向数据库注入数据,然后再进一步创建一个功能齐全的 API。我们走吧!

使用 MongoDB CRUD 注入数据

我们将在 nuxt 应用数据库中注入一些数据,使用您在上一节中了解的 MongoDB CRUD 操作。

插入文档

我们可以使用以下insertOneinsertMany方法插入新文件:

  • 插入单个文档我们可以这样插入一个新文档:
> db.<collection>.insertOne(<document>)

让我们插入一个具有以下代码的文档:

db.user.insertOne(
  {
    name: "Alexandre",
    age: 30,
    slug: "alexandre",
    role: "admin",
    status: "ok"
  }
)

您应该得到与此类似的结果:

{
  "acknowledged" : true,
  "insertedId" : ObjectId("5ca...")
}
  • 插入多个文档我们可以像这样插入多个新文档:
> db.<collection>.insertMany([<document>,<document>,<document>,...])

让我们插入两个具有以下代码的文档:

> db.user.insertMany([
  {
    name: "Pooya",
    age: 25,
    slug: "pooya",
    role: "admin",
    status: "ok"
  },
  {
    name: "Sébastien",
    age: 22,
    slug: "sebastien",
    role: "writer",
    status: "pending"
  }
])

您应该得到与此类似的结果:

{
  "acknowledged" : true,
  "insertedIds" : [
    ObjectId("5ca..."),
    ObjectId("5ca...")
  ]
}

user集合中添加文档之后,我们希望获取它们,这可以通过使用下一节中的读取操作来完成。

查询文档

我们可以使用find方法取文件,如下所示:

  • 选择一个集合中的所有文档我们可以这样从一个集合中获取所有文档:
> db.<collection>.find()

此操作与以下 SQL 语句相同:

SELECT  FROM <table>

让我们从user集合中取出所有文档,如下所示:

> db.user.find()

您应该得到与此类似的结果:

{ "_id" : ObjectId("5ca..."), "name" : "Alexandre", "slug" :
 "alexandre", ... }
{ "_id" : ObjectId("5ca..."), "name" : "Pooya", "slug" : "pooya", ... }
{ "_id" : ObjectId("5ca..."), "name" : "Sébastien", "slug" : 
 "sebastien", ... }
  • 指定相等条件我们可以从这样的集合中获取特定文档:
> db.<collection>.find(<query>, <projection>)

您可以看到,我们使用了与上一个示例相同的find方法,但传入了<query>参数中的选项来过滤与特定查询匹配的文档。例如,下一行选择了status等于ok的文档:

> db.user.find( { status: "ok" } )

此操作与以下 SQL 语句相同:

SELECT  FROM user WHERE status = "ok"

您应该得到与此类似的结果:

{ "_id" : ObjectId("5ca..."), "name" : "Alexandre", ... "status" : "ok" }
{ "_id" : ObjectId("5ca..."), "name" : "Pooya", ... "status" : "ok" }
  • 使用查询运算符指定条件:我们也可以在find方法的<query>参数中使用 MongoDB 查询选择器,如$eq$gt$in。例如,以下行获取的文档中,status等于okpending
> db.user.find( { status: { $in: [ "ok", "pending" ] } } )

此操作与以下 SQL 语句相同:

SELECT  FROM user WHERE status in ("ok", "pending")

You can find out more information about the query selectors at https://docs.mongodb.com/manual/reference/operator/query/query-selectors.

  • 指定和条件您还可以将过滤器与查询选择器混合使用。例如,status等于okage小于($lt)30 时,以下行获取文档:****
> db.user.find( { status: "ok", age: { $lt: 30 } } )

您应该得到与此类似的结果:

{ "_id" : ObjectId("5ca..."), "name" : "Pooya", "age" : 25, ... }

此操作与以下 SQL 语句相同:

SELECT  FROM user WHERE status = "ok" AND age < 30
  • 指定 OR 条件您还可以使用$or选择器创建 OR 条件,以获取至少符合一个条件的文档。例如,status等于okage小于($lt)30 时,以下行获取文档:
> db.user.find( { $or: [ { status: "ok" }, { age: { $lt: 30 } } ] } )

此操作与以下 SQL 语句相同:

SELECT  FROM user WHERE status = "ok" OR age < 30

您应该得到与此类似的结果:

{ "_id" : ObjectId("5ca..."), "name" : "Pooya", "age" : 25, ... }

You can find out more about the query and projection operators at https://docs.mongodb.com/manual/reference/operator/query/ and the $or selector at https://docs.mongodb.com/manual/reference/operator/query/logical.

现在,我们感兴趣的下一件事是更新现有文档,让我们继续下一节。

更新文件

我们可以使用updateOneupdateMany方法更新现有文件,如下所示:

  • 更新单个文档:我们可以像这样更新现有文档:
> db.<collection>.updateOne(<filter>, <update>, <options>)

让我们用<update>参数中的$set操作符更新<filter>参数中name等于Sébastien的文档,如下所示:

> db.user.updateOne(
   { name: "Sébastien" },
   {
     $set: { status: "ok" },
     $currentDate: { lastModified: true }
   }
)

您应该得到以下结果:

{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }

$set运算符用于用新值替换字段的值。它采用以下格式:

{ $set: { <field1>: <value1>, ... } }

$currentDate运算符用于将字段的值设置为当前日期。它返回的值可以是人类可读的日期(默认值),例如2013-10-02T01:11:18.965Z或时间戳,例如1573612039

You can find out more information about the $set operator at https://docs.mongodb.com/manual/reference/operator/update/set/. You can find out more information about $currentDate at https://docs.mongodb.com/manual/reference/operator/update/currentDate/.

  • 更新多个单据:我们可以像这样更新多个已有单据:
> db.<collection>.updateMany(<filter>, <update>, <options>)

让我们更新文档,其中statusok

> db.user.updateMany(
   { "status": "ok" },
   {
     $set: { status: "pending" },
     $currentDate: { lastModified: true }
   }
)

您应该得到以下结果:

{ "acknowledged" : true, "matchedCount" : 3, "modifiedCount" : 3 }

You can find out more information about the update operators at https://docs.mongodb.com/manual/reference/operator/update/.

  • 替换文档:我们可以替换现有文档的内容,除了_id字段,如下所示:
> db.<collection>.replaceOne(<filter>, <replacement>, <options>)

让我们用<replacement>参数中的全新文档替换name等于Pooya的文档,如下所示:

> db.user.replaceOne(
    { name: "Pooya" },
    {
      name: "Paula",
      age: "31",
      slug: "paula",
      role: "admin",
      status: "ok"
    }
)

您应该得到以下结果:

{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }

在学习了如何更新现有文档之后,接下来应该学习的是如何删除现有文档。让我们进入下一节。

删除文件

我们可以通过deleteOnedeleteMany两种方式删除现有单据,具体如下:

  • 只删除一个符合条件:的单据,我们可以这样删除一个已有的单据:
> db.<collection>.deleteOne(<filter>, <options>)

我们删除status字段等于pending的文档,如下所示:

> db.user.deleteOne( { status: "pending" } )

您应该得到以下结果:

{ "acknowledged" : true, "deletedCount" : 3 }
  • 删除符合条件:的单据我们可以删除多个现有单据,如下所示:
> db.<collection>.deleteMany(<filter>, <options>)

我们删除status字段等于ok的文档:

> db.user.deleteMany({ status : "ok" })

您应该得到以下结果:

{ "acknowledged" : true, "deletedCount" : 2 }
  • 删除所有文档:我们可以通过向deleteMany方法传递空过滤器来删除集合中的所有文档,如下所示:
> db.<collection>.deleteMany({})

我们删除user集合中的所有文档,代码如下:

> db.user.deleteMany({})

您应该得到以下结果:

{ "acknowledged" : true, "deletedCount" : 1 }

做得好!您已经成功地完成了这些部分中的 MongoDB CRUD 操作。您可以在找到更多其他方法 https://docs.mongodb.com/manual/reference/method/js-collection/ 。在下一节中,我们将指导您如何使用 MongoDB 驱动程序将 CRUD 操作与服务器端框架集成。我们走吧。

集成 MangGDB 与膝关节炎

我们研究了一些 MongoDB 查询,以通过 MongoDB Shell 执行 CRUD 操作。现在,我们只需要 MongoDB 驱动程序来帮助我们连接到 MongoDB 服务器并执行与 MongoDB Shell 相同的 CRUD 操作。我们将在我们的应用中安装这个驱动程序,作为我们服务器端框架的依赖关系。膝关节炎。

安装 MongoDB 驱动程序

Node.js 应用的官方 MongoDB 驱动程序是mongodb。它是一个高级 API,构建在 MongoDB 核心驱动程序mongodb-core(一个低级 API)之上。前者面向最终用户,后者面向 MongoDB 库开发人员。mongodb包含使 MongoDB 连接、CRUD 操作和身份验证变得容易的抽象和帮助,而mongodb-core仅包含 MongoDB 拓扑连接、核心 CRUD 操作和身份验证的基本管理。

有关这两个软件包的更多信息,请访问以下网站:

我们可以使用 npm 安装 MongoDB 驱动程序:

$ npm i mongodb

接下来,我们将通过下一节中的一个快速示例来研究如何使用它。

使用 MongoDB 驱动程序创建一个简单的应用

让我们使用 MongoDB 驱动程序设置一个简单的应用来执行简单的连接检查。在本测试中,我们将使用上一章介绍的背包构建系统来运行测试。那么,让我们从以下步骤开始:

  1. 如前一节所示安装 MongoDB 驱动程序,然后安装 Backpack 和 cross env:
$ npm i backpack-core
$ npm i cross-env
  1. 创建一个/src/文件夹作为默认条目目录,并在其中创建一个index.js文件,然后从 Node.js 导入 MongoDB 驱动程序和断言模块,如下所示:
// src/index.js
import { MongoClient } from 'mongodb'
import assert from 'assert'

const url = 'mongodb://localhost:27017'
const dbName = 'nuxt-app'

在这一步中,我们还应该提供 MongoDB 连接的详细信息:MongoDB 服务器默认地址为mongodb://localhost:27017,我们要连接的数据库为nuxt-app

Note that Assert is a Node.js built-in module that comes with a set of assertion functions for unit testing your code, so we don't have to install this module. If you want to find out more about this module, please visit https://nodejs.org/api/assert.html#assert_assert.

  1. 接下来,在 MongoDB 服务器中建立与数据库的连接,并使用 Assert 确认连接,如下所示:
// src/index.js
MongoClient.connect(url, {
  useUnifiedTopology: true,
  useNewUrlParser: true 
  }, (err, client) => {
  assert.equal(null, err)
  console.log('Connected to the MongoDB server')

  const db = client.db(dbName)
  client.close()
})

在本例中,我们使用了assert模块中的equal方法来确保err回调为null,然后使用client回调创建数据库实例。每当我们完成一项任务时,我们都应该关闭与close方法的连接。

  1. 如果您使用npm run dev在您的终端上运行此连接测试,您应该在您的终端上获得以下输出:
Connected successfully to server

You can find this simple example in /chapter-9/mongo-driver/ in our GitHub repository.

请注意,我们使用任何身份验证连接到 MongoDB,因为我们还没有保护 MongoDB。在本书的最后一章中,您将学习如何设置新的管理用户来保护您的 MongoDB—第 18 章创建带有 CMS 和 GraphQL的 Nuxt 应用。为了使您的学习曲线变得平坦,并在本章接下来的章节中加快开发过程,我们将选择不使用 MongoDB。现在,让我们深入研究下一节中如何配置 MunGDB 驱动程序。

配置 MongoDB 驱动程序

从上一节的代码可以看出,我们应该始终导入MongoClient,在执行 MongoDB CRUD 任务时提供 MongoDB 服务器 URL、数据库名称等。这可能是乏味和适得其反的。让我们通过以下步骤将前面的 MongoDB 连接代码抽象为一个类:

  1. 将数据库连接详细信息抽象到文件中:
// server/config/mongodb.js
const database = {
  host: 'localhost',
  port: 27017,
  dbname: 'nuxt-app'
}

export default {
  host: database.host,
  port: database.port,
  dbname: database.dbname,
  url: 'mongodb://' + database.host + ':' + database.port
}
  1. 创建一个class函数来构造数据库连接,这样我们就不必在执行 CRUD 操作时重复这个过程。我们还在class函数中构造一个objectId属性,用于存储解析来自客户端的 ID 数据所需的ObjectId方法,以便该 ID 数据将成为字符串中的对象:
// server/mongo.js
import mongodb from 'mongodb'
import config from './config/mongodb'

const MongoClient = mongodb.MongoClient

export default class Mongo {
  constructor () {
    this.connection = null
    this.objectId = mongodb.ObjectId
  }

  async connect () {
    this.connection = await MongoClient.connect(config.url, {
      useUnifiedTopology: true,
      useNewUrlParser: true
    })
    return this.connection.db(config.dbname)
  }

  close () {
    this.connection.close()
  }
}
  1. 导入class并用new语句实例化,如下所示:
import Mongo from './mongo'
const mongo = new Mongo()

例如,我们可以在我们的 API 路由中导入它,我们需要连接到 MongoDB 数据库以执行 CRUD 操作,如下所示:

// server/routes.js
import Router from 'koa-router'
import Mongo from './mongo'
const mongo = new Mongo()
const router = new Router({ prefix: '/api' })

router.post('/user', async (ctx, next) => {
  //...
})

在使用 MUGODB 驱动程序和服务器端框架 KOA 创建 CRUD 操作之前,我们应该了解膝关节炎的 T0 和 T1 方法。让我们开始吧。

理解 ObjectId 和 ObjectId 方法

ObjectId是一个快速生成且可能唯一的值,MongoDB 将其用作集合中的主键。它由 12 个字节组成;时间戳以前 4 个字节记录创建ObjectId值的时间。它存储在集合中每个文档的唯一_id字段中。如果在注入文档时未声明此_id字段,则会自动生成此字段。另一方面,ObjectId(<hexadecimal>)是一个 MongoDB 方法,我们可以使用它返回一个新的ObjectId值,并将一个ObjectId值从字符串解析为一个对象。下面是一个例子:

// Pseudo code
var id = '5d2ba2bf089a7754e9094af5'
console.log(typeof id) // string
console.log(typeof ObjectId(id)) // object

在前面的伪代码中,您可以看到我们从ObjectId方法创建的对象中使用getTimestamp方法从ObjectId值中获取时间戳。下面是一个例子:

// Pseudo code
var object = ObjectId(id)
var timestamp = object.getTimestamp()
console.log(timestamp) // 2019-07-14T21:46:39.000Z

有关ObjectIdObjectId方法的更多信息,请查看以下链接:

现在,让我们在接下来的部分中编写一些使用 MongoDB 驱动程序的 CRUD 操作。首先,我们将编写注入文档的操作。

注入一个文档

在开始之前,我们应该看看我们将要创建的每个路由所需的代码结构:

// server/routes.js
router.get('/user', async (ctx, next) => {
  let result
  try {
    const connection = await mongo.connect()
    const collectionUsers = connection.collection('users')
    result = await collectionUsers...
    mongo.close()
  } catch (err) {
    ctx.throw(500, err)
  }
  ctx.type = 'json'
  ctx.body = result
})

让我们讨论一下结构:

  • 捕获和抛出错误:当我们使用async/await语句而不是Promise对象进行异步操作时,我们必须始终将它们包装在try/catch块中以处理错误:
try {
  // async/await code
} catch (err) {
  // handle error
}
  • 连接 MongoDB 数据库和集合:在执行任何 CRUD 操作之前,我们必须建立连接并连接到我们想要操作的特定集合。在我们的例子中,集合是users
const connection = await mongo.connect()
const collectionUsers = connection.collection('users')
  • 执行 CRUD 操作:这是我们使用 MongoDB API 方法读取、注入、更新和删除用户的地方:
result = await collectionUsers...
  • 关闭 MongoDB 连接:我们必须确保在 CRUD 操作后关闭连接:
mongo.close()

现在,让我们使用前面的代码结构在以下步骤中注入新用户:

  1. 使用post方法创建一个路由,注入一个新的用户文档:
// server/routes.js
router.post('/user', async (ctx, next) => {
  let result
  //...
})
  1. post路由内,在使用 MongoDB 执行 CRUD 操作之前,对我们从客户端收到的密钥和值进行检查:
let body = ctx.request.body || {}

if (body.name === undefined) {
  ctx.throw(400, 'name is undefined')
}
if (body.slug === undefined) {
  ctx.throw(400, 'slug is undefined')
}
if (body.name === '') {
  ctx.throw(400, 'name is required')
}
if (body.slug === '') {
  ctx.throw(400, 'slug is required')
}
  1. 在允许将新文档注入到user集合之前,我们希望确保slug值不存在。为此,我们需要使用带有slug键的findOneAPI 方法。如果结果为正,则表示slug值已被其他用户文档获取,因此我们向客户端抛出一个错误:
const found = await collectionUsers.findOne({
  slug: body.slug
})
if (found) {
  ctx.throw(404, 'slug has been taken')
}
  1. 如果slug是唯一的,那么我们使用insertOneAPI 方法将提供的数据注入一个新文档:
result = await collectionUsers.insertOne({
  name: body.name,
  slug: body.slug
})

注入文档后,我们需要做的下一件事是获取和查看我们注入的文档,这将在下一节中进行。

获取所有文档

将用户添加到users集合后,我们可以通过第 8 章添加服务器端框架中创建的路由来检索全部或仅检索其中一个用户。现在,我们只需使用与上一节相同的代码结构对它们进行重构,以便从数据库中获取真实数据:

  1. 使用get方法重构列出所有用户文档的路径:
// server/routes.js
router.get('/users', async (ctx, next) => {
  let result
  //...
})
  1. get路由内,使用findAPI 方法从user集合中取出所有文档:
result = await collectionUser.find({
}, {
  // Exclude some fields
}).toArray()

如果要从查询结果中排除字段,请使用projection键和0值作为不希望在结果中显示的字段。例如,如果您不希望结果中的每个文档上都有_id字段,请执行以下操作:

projection:{ _id: 0 }
  1. 使用get方法重构获取用户文档的路径:
// server/routes.js
router.get('/users/:id', async (ctx, next) => {
  let result
  //...
})
  1. 使用带_idfindOne方法获取单个文档。我们必须用ObjectId方法解析id字符串,我们在class函数中的constructor函数中有一个副本作为objectId
const id = ctx.params.id
result = await collectionUsers.findOne({
  _id: mongo.objectId(id)
}, {
  // Exclude some fields
})

mongo.objectId(id)方法将id字符串解析为ObjectID对象,然后我们可以使用它从集合中查询文档。既然我们可以获取我们创建的文档,接下来我们需要做的就是更新它们。让我们在下一节讨论它。

更新一份文件

将用户添加到users集合后,我们还可以通过以下步骤使用与上一节相同的代码结构对其进行更新:

  1. 使用put方法创建路由,更新现有用户文档,如下所示:
// server/routes.js
router.put('/user', async (ctx, next) => {
  let result
  //...
})
  1. 在更新文档之前,我们要确保slug值是唯一的。因此,在put路由中,我们搜索与findOneAPI 和$ne的匹配,以排除我们正在更新的文档。如果不匹配,则我们继续使用updateOneAPI 方法更新文档:
const found = await collectionUser.findOne({
  slug: body.slug,
  _id: { $ne: mongo.objectId(body.id) }
})
if (found) {
  ctx.throw(404, 'slug has been taken')
}

result = await collectionUser.updateOne({
  _id: mongo.objectId(body.id)
}, {
   $set: { name: body.name, slug: body.slug },
   $currentDate: { lastModified: true }
})

我们在这个 CRUD 操作中使用了三个操作符:$set操作符、$currentDate操作符和$ne选择器。以下是更新文档时经常使用的一些更新运算符和查询选择器:

  • 更新操作符:使用$set操作符将字段的值替换为以下格式的新指定值:
{ $set: { <field1>: <value1>, ... } }

$currentDate运算符用于将当前日期设置为指定字段,可以是 BSON 日期类型(默认值)或 BSON 时间戳类型,格式如下:

{ $currentDate: { <field1>: <typeSpecification1>, ... } }

For more information about these two and other update operators, please visit https://docs.mongodb.com/manual/reference/operator/update/.

  • 查询选择器:选择器$ne用于选择字段值不等于指定值的单据,包括不包含字段的单据。下面是一个例子:
db.user.find( { age: { $ne: 18 } } )

此查询将选择user集合中age字段值不等于18的所有单据,包括不包含age字段的单据。

For more information about this and other query selectors, please visit https://docs.mongodb.com/manual/reference/operator/query/.

现在,让我们来看看如何删除在下一节中已经创建的文档。

删除一份文件

最后,我们还将使用与上一节相同的代码结构,通过以下步骤从users集合中删除现有用户:

  1. 使用del方法创建路由,删除现有用户文档:
// server/routes.js
router.del('/user', async (ctx, next) => {
  let result
  //...
})
  1. 在使用deleteOneAPI 方法删除文档之前,在del路径内,我们一如既往地使用findOneAPI 方法查找此文档,以确保我们首先将其保存在user集合中:
let body = ctx.request.body || {}
const found = await collectionUser.findOne({
  _id: mongo.objectId(body.id)
})
if (!found) {
  ctx.throw(404, 'no user found')
}

result = await collectionUser.deleteOne({
  _id: mongo.objectId(body.id)
})

做得好!您已经成功地编写了 MongoDB CRUD 操作并将其集成到 API(Koa)中。本章的最后一部分涉及将这些操作与 Nuxt 页面集成。让我们在下一节讨论这个问题。

与 Nuxt 页面集成

我们已经准备好了服务器端,现在我们需要客户端的用户界面,以便发送和获取数据。我们将在/pages/users/目录中创建三个新页面。这是我们的结构:

users
├── index.vue
├── _id.vue
├── add
│ └── index.vue
├── update
│ └── _id.vue
└── delete
  └── _id.vue

一旦我们有了适当的结构,我们就可以在下面的小节中从 Nuxt 端(客户端)创建页面并编写 CRUD 任务。让我们从下一节中的创建CRUD 任务开始。

创建用于添加新用户的添加页面

我们将创建此页面与服务器端POST路由/api/user/进行通信,以在以下步骤中添加新用户:

  1. 创建一个表单来收集<template>块中的新用户数据,如下所示:
// pages/users/add/index.vue
<form v-on:submit.prevent="add">
  <p>Name: <input v-model="name" type="text" name="name"></p>
  <p>Slug: <input v-model="slug" type="text" name="slug"></p>
  <button type="submit">Add</button>
  <button v-on:click="cancel">Cancel</button>
</form>
  1. <script>块中创建add方法发送数据到服务器,创建cancel方法取消表单,如下所示:
// pages/users/add/index.vue
export default {
  methods: {
    async add () {
      let { data } = await axios.post('/api/user/', {
        name: this.name,
        slug: this.slug,
      })
    },
    cancel () {
      this.$router.push('/users/')
    }
  }
}

通过这两个步骤,我们成功地在客户端(Nuxt)和服务器端(API)上建立了创建CRUD 任务。因此,现在您可以使用刚才创建的表单从localhost:3000/users/add的客户端向数据库添加新用户,以收集用户数据并将其发送到localhost:3000/api/user/的 APIPOST路由。在能够添加新用户之后,我们应该继续在客户端执行更新CRUD 任务。让我们开始吧。

创建用于更新现有用户的更新页面

更新页面基本上与添加页面非常相似。此页面将与服务器端PUT路由/api/user/通信,通过以下步骤更新现有用户:

  1. 创建一个表单以显示现有数据并在<template>块中收集新数据。更新页面的区别在于我们绑定到<form>元素的方法:
// pages/users/update/_id.vue
<form v-on:submit.prevent="update">
  //...
  <button type="submit">Update</button>
</form>
  1. 创建一个update方法,将数据发送到<script>块中的服务器。我们将使用asyncData方法获取现有数据,如下所示:
// pages/users/update/_id.vue
export default {
  async asyncData ({ params, error }) {
    let { data } = await axios.get('/api/users/' + params.id)
    let user = data.data
    return { 
      id: user._id, 
      name: user.name, 
      slug: user.slug,
    }
  },
  methods: {
    async update () {
      let { data } = await axios.put('/api/user/', {
        name: this.name,
        slug: this.slug,
        id: this.id,
      })
    }
  }
}

同样,我们在客户端(Nuxt)和服务器端(API)的这两个步骤中成功地建立了更新 CRUD 任务。因此,现在您可以使用表单从localhost:3000/users/update的客户端更新数据库中的现有用户,收集用户数据并发送到localhost:3000/api/user/的 APIPUT路由。在能够更新用户之后,我们现在应该转到客户端的删除CRUD 任务。让我们开始吧。

创建用于删除现有用户的删除页面

此页面将与服务器端DELETE路由/api/user/通信,删除现有用户:

  1. 创建一个<button>元素,我们可以使用它删除<template>块中的文档。我们不需要表单来发送数据,因为我们可以通过remove方法收集数据(这只是文档_id数据)。我们只需按下按钮即可触发此方法,如下所示:
// pages/users/delete/_id.vue
<button v-on:click="remove">Delete</button>
  1. 创建remove方法将数据发送到服务器,如<script>块中所述。但首先,我们需要使用asyncData方法获取现有数据:
// pages/users/delete/_id.vue
export default {
 async asyncData ({ params, error }) {
    // Fetch the existing user
    // Same as in update page
  },
  methods: {
    async remove () {
      let payload = { id: this.id }
      let { data } = await axios.delete('/api/user/', {
        data: payload,
      })
    }
  }
}

最后,我们在客户端(Nuxt)和服务器端(API)的这两个步骤中成功地建立了删除CRUD 任务。现在您可以通过发送用户数据从localhost:3000/users/delete客户端的数据库中删除现有用户,该数据仅为 ID,并发送到localhost:3000/api/user/API 的DELETE路由。因此,如果您使用npm run dev启动应用,您应该会看到它在localhost:3000运行。

导航到以下路径以添加、更新、读取和删除用户:

  • localhost:3000/users用于读取/列出所有用户
  • localhost:3000/users/add用于注入新用户
  • localhost:3000/users/update/<id>用于根据用户 ID 更新现有用户
  • localhost:3000/users/delete/<id>用于根据用户 ID 删除现有用户

做得好!你终于完成了我们在本章中设定的里程碑。MongoDB 可能是初学者学习的一个非常重要的主题,但是如果您遵循了我们在本章中设置的指南和里程碑,您可以轻松创建一个相当不错的 API。当您需要在本书中解释的 CRUD 操作之外冒险时,请使用我们提供的链接。现在让我们总结一下您在本章学到的知识。

You can find the code we have created for this chapter in /chapter-9/nuxt-universal/koa-mongodb/axios/ in our GitHub repository.

总结

在本章中,您已经学习了如何在本地计算机上安装 MongoDB,并且在 MongoDB Shell 上使用了一些基本的 MongoDB 查询来执行 CRUD 操作。您还学习了如何安装和使用 MangoDB 驱动程序来连接服务器端框架中的 MangGDB,并且编写了代码来执行膝关节炎环境中的 CRUD 操作。最后,您已经从客户端 Nuxt 创建了首页,用于向 MangoDB 数据库添加新用户,并通过与您开发的与膝关节炎(API)相关的 API 进行通信来更新和删除现有用户。

在下一章中,我们将探索 Vuex 应用商店并在 Nuxt 应用中使用它。在安装 Vuex 体系结构并在 Vue 应用中编写简单的 Vuex 存储之前,您将了解 Vuex 体系结构。在使用这些概念在 Nuxt 应用中编写 Vuex 存储之前,您还将了解 Vuex 核心概念,包括状态、getter、操作和模块。我们将引导您完成这些任务,请继续关注。******