大多数应用程序依赖于数据库管理系统,简称为数据库,用于组织和访问一系列数据集。在本章中,你将使用非关系型数据库MongoDB及其伴随的对象映射工具Mongoose。
由于MongoDB返回的数据是JSON格式,并使用JavaScript进行数据库查询,因此它是全栈JavaScript开发人员的自然选择。在接下来的部分中,你将学习如何创建一个Mongoose模型,通过它你可以查询数据库,简化与MongoDB的交互,并制作连接前端和后端数据库的中间件。你还将编写服务函数,以实现对数据库的四种CRUD(创建、读取、更新、删除)操作。
在第七章的练习中,你将向第六章中创建的GraphQL API添加数据库,替换当前的静态数据存储。
应用程序如何使用数据库和对象关系映射器
一个应用程序需要数据库来存储和操作数据。在本书中,我们的应用程序的API返回的只是保存在文件中的预定义数据集,无法更改。我们在请求中使用参数来添加数据集,但无法在不同的API调用之间存储数据(称为持久化数据)。例如,如果我们想更新应用程序的天气信息,我们需要一个数据库来持久化数据,以便下一个API调用可以读取它。在全栈开发中,我们常常使用数据库来存储与用户相关的数据。另一个数据库的例子是你的电子邮件客户端用来存储消息的数据库。
要使用数据库,首先需要连接到它并进行身份验证。一旦我们获得数据访问权限,就可以执行查询以请求某些数据集。查询返回的结果包含我们的应用程序可以显示或以其他方式使用的数据。这些步骤在实际操作中如何工作取决于所使用的具体数据库。
使用数据库的API进行数据查询往往是笨拙的,因为即使只是简单地建立和维护连接,通常也需要大量样板代码。因此,我们经常使用对象关系映射器或对象数据建模工具,通过抽象一些细节来简化与数据库的工作。例如,MongoDB的Mongoose对象数据建模工具处理数据库连接,为我们节省了每次交互期间检查数据库连接的步骤。
Mongoose还使处理MongoDB运行在单独的数据库服务器上的事实变得更容易。处理分布式系统需要进行异步调用,你在第二章中已经学习过。使用Mongoose,我们可以使用面向对象的async/await接口访问数据,而不是使用笨拙的回调函数。
此外,MongoDB是无模式的;它不要求我们预先定义并严格遵守一个模式。虽然方便,但这种灵活性也是错误的常见来源,特别是在大型应用程序或开发人员轮换的项目中。在第三章中,我们讨论了使用TypeScript为JavaScript添加类型的好处。Mongoose类似地对MongoDB的数据模型进行类型定义和验证,你将在后面章节的“定义Mongoose模型”部分中发现这一点。
关系型和非关系型数据库
数据库可以以多种方式组织数据,主要分为两大类:关系型和非关系型。关系型数据库,如MySQL和PostgreSQL,将数据存储在一个或多个表中。你可以将这些数据库想象成类似于Excel电子表格。像在Excel中一样,每个表都有一个唯一的名称,包含列和行。列定义了存储在列中的所有数据的属性,如数据类型,行则包含实际的数据集,每个数据集都有一个唯一ID。关系型数据库使用某种变体的结构化查询语言(SQL)进行数据库操作。
MongoDB是一种非关系型数据库。与传统的关系型数据库不同,它以JSON文档而不是表的形式存储数据,并且不使用SQL。有时称为NoSQL,非关系型数据库可以以多种不同的格式存储数据。例如,流行的NoSQL数据库Redis和Memcached使用键值存储,这使得它们性能高且易于扩展。因此,它们常用于内存缓存。另一种NoSQL数据库,Neo4j,是一种图数据库,使用图论将数据存储为节点,这一概念在第六章中提到过。这些只是非关系型数据库的一些例子。
MongoDB是最广泛使用的文档数据库;它不使用表、行和列,而是使用集合、文档和字段来组织数据。字段是数据库中的最小单位。它定义了数据类型和其他属性,包含实际数据。你可以将其视为SQL表中的列。文档由字段组成,类似于SQL表中的行。我们有时称它们为记录,MongoDB使用BSON(一种JSON对象的二进制表示)来存储它们。集合大致相当于SQL表,但不是使用行和列,而是聚合文档。
由于非关系型数据库可以以不同的格式存储数据,每种数据库使用特定的、优化的查询语言进行CRUD操作。这些低级API专注于访问和操作数据,而不一定关注开发者体验。相比之下,对象关系映射器提供了对查询语言的高级抽象,接口简洁和简化。因此,虽然MongoDB有MongoDB查询语言(MQL),我们将使用Mongoose访问它。
设置MongoDB和Mongoose 在开始使用MongoDB和Mongoose之前,你必须将它们添加到你的示例项目中。为了简化操作,我们将使用内存中的MongoDB实现,而不是在我们的机器上安装和维护一个真实的数据库服务器。这对于测试本章的例子是合适的,但不适用于部署实际应用程序,因为它不会在重启之间持久化数据。当你在第二部分构建Food Finder应用程序时,你将获得设置真实MongoDB服务器的经验。第十一章将向你展示如何使用包含MongoDB服务器的预构建Docker容器。
在第六章重构的Next.js应用的根目录下运行以下命令:
$ npm install mongodb-memory-server mongoose
然后在根目录下(package.json文件旁边)创建两个新文件夹:一个用于Mongoose代码,名为mongoose,包含子文件夹weather;另一个名为middleware,用于保存必要的中间件。
定义 Mongoose 模型
为了验证数据的完整性,我们必须创建一个基于模式的 Mongoose 模型,该模型作为数据库中 MongoDB 集合的直接接口。与数据库的所有交互都将通过该模型进行。然而,在创建模型之前,我们需要先创建模式(schema)本身,它定义了数据库数据的结构,并将 Mongoose 实例映射到集合中的文档。
我们的 Mongoose 模式将匹配第 6 章为 GraphQL API 创建的模式。这是因为我们将在第 7 章的练习中将 GraphQL API 连接到数据库,从而用查询自数据库的数据集替换静态 JSON 对象。
接口
在使用 TypeScript 编写 Mongoose 模型和模式之前,让我们声明一个 TypeScript 接口。如果没有匹配的接口,我们将无法为 TSC 类型化模型或模式,代码也无法编译。将以下代码粘贴到 mongoose/weather/interface.ts 文件中:
export declare interface WeatherInterface {
zip: string;
weather: string;
tempC: string;
tempF: string;
friends: string