原文:
zh.annas-archive.org/md5/1432c32f9d6dcbddfc63da998dd364ec译者:飞龙
前言
JavaScript 已经成为当今和未来最重要的语言之一。
在过去几年中,JavaScript 的崛起非常迅速,它已成为现代网络应用开发中的强大语言。
MEVN 是除了 MEAN 和 MERN 之外,用于开发现代网络应用的一种技术栈。本书提供了一种逐步构建全栈网络应用程序的方法,使用 MEVN 技术栈,即 MongoDB、Express.js、Vue.js 和 Node.js。
本书将提供 Node.js 和 MongoDB 的基本概念,接着是构建 Express.js 应用程序并实现 Vue.js。
在这本书中,我们将涵盖以下内容:
-
了解技术栈——MongoDB、Node.js、Express.js 和 Vue.js
-
构建 Express.js 应用程序
-
学习什么是 REST API 以及如何实现它们
-
学习如何在 Express.js 应用程序中将 Vue.js 作为前端层使用
-
在应用程序中添加一个认证层
-
添加自动化脚本和测试
这本书面向的对象
这本书是为对学习如何使用 JavaScript 作为唯一编程语言构建全栈应用程序感兴趣的网页开发者而设计的,使用的技术栈是:MongoDB、Express.js、Vue.js 和 Node.js。
这本书适合对 HTML、CSS 和 JavaScript 有基本知识的初学者和中级开发者。如果你是网页或全栈 JavaScript 开发者,并且已经尝试过传统的技术栈,如 LAMP、MEAN 或 MERN,并希望探索一个使用现代网络技术的全新技术栈,那么这本书适合你。
这本书涵盖的内容
第一章,MEVN 简介,介绍了 MEVN 栈以及构建应用程序基础所需的不同工具的安装。
第二章,构建 Express 应用程序,介绍了 Express.js,解释了 模型、视图、控制器(MVC)结构的概念,并展示了如何使用 Express.js 和 MVC 结构设置应用程序。
第三章,MongoDB 简介,重点介绍了 Mongo 及其查询,介绍了 Mongoose 以及使用 Mongoose 进行 创建、读取、更新 和 删除(CRUD)操作的性能。
第四章,REST API,介绍了 REST 架构以及 RESTful API 的概念。本章还介绍了不同的 HTTP 动词以及开发 REST API。
第五章,构建真实应用程序,介绍了 Vue.js 并展示了如何使用 MEVN 中的所有技术构建一个完全工作的动态应用程序。
第六章,使用 Passport.js 进行身份验证,讨论了 Passport.js,并描述了如何实现 JWT 和本地策略以在应用程序中添加身份验证层。
第七章,Passport.js OAuth 策略,介绍了 OAuth 策略,并指导您实现 Facebook、Twitter、Google 和 LinkedIn 的 Passport.js 策略。
第八章,Vuex 简介,介绍了 Vuex 的核心概念——状态、获取器、突变和动作。它还描述了您如何在应用程序中实现它们。
第九章,测试 MEVN 应用程序,解释了单元测试和端到端测试是什么,并指导您为应用程序的不同方面编写单元测试和自动化测试。
第十章,Go Live,解释了什么是持续集成,并指导您如何设置与应用程序的持续集成服务,并在 Heroku 上部署应用程序。
要充分利用本书
如果您具备以下技能,本书将更有益:
-
了解 HTML、CSS 和 JavaScript
-
了解 Vue.js 和 Node.js 是一个加分项
-
了解如何使用 MEAN 和 MERN 堆栈构建 Web 应用程序是一个加分项
下载示例代码文件
您可以从 www.packtpub.com 的账户下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
在 www.packtpub.com 登录或注册。
-
选择支持选项卡。
-
点击代码下载与勘误表。
-
在搜索框中输入本书的名称,并遵循屏幕上的说明。
文件下载后,请确保使用最新版本的以下软件解压缩或提取文件夹:
-
Windows 上的 WinRAR/7-Zip
-
Mac 上的 Zipeg/iZip/UnRarX
-
7-Zip/PeaZip for Linux
本书代码包也托管在 GitHub 上,网址为 github.com/PacktPublishing/Full-Stack-Web-Development-with-Vue.js-and-Node。如果代码有更新,它将在现有的 GitHub 仓库中更新。
我们还有来自我们丰富的图书和视频目录的其他代码包,可在 github.com/PacktPublishing/ 找到。查看它们吧!
使用的约定
本书使用了多种文本约定。
CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“一个模块是可以由 Node.js 使用require命令加载的,并且有一个命名空间。模块与其关联的package.json文件。”
代码块设置如下:
extends layout
block content
h1= title
p Welcome to #{title}
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
var index = require('./routes/index');
var users = require('./routes/users');
var app = express();
// Require file system module
var fs = require('file-system');
任何命令行输入或输出都应如下编写:
$ mkdir css
$ cd css
粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“只需点击“继续”,直到安装完成。”
警告或重要注意事项看起来像这样。
小贴士和技巧看起来像这样。
联系我们
我们始终欢迎读者的反馈。
一般反馈:请发送电子邮件至 feedback@packtpub.com,并在邮件主题中提及书籍标题。如果您对本书的任何方面有疑问,请发送电子邮件至 questions@packtpub.com。
勘误:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告,我们将不胜感激。请访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。
盗版:如果您在互联网上发现我们作品的任何非法副本,我们将不胜感激,如果您能提供位置地址或网站名称,我们将不胜感激。请通过链接至材料的方式与我们联系 copyright@packtpub.com。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问 authors.packtpub.com。
评论
请留下评论。一旦您阅读并使用过这本书,为什么不在您购买它的网站上留下评论呢?潜在读者可以查看并使用您的客观意见来做出购买决定,Packt 可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!
如需更多关于 Packt 的信息,请访问 packtpub.com。
第一章:MEVN 简介
Mongo, Express, Vue.js, and Node.js(MEVN)是一组 JavaScript 技术,就像MongoDB、Express、Angular和Node.js(MEAN)一样,以及像MongoDB、Express、React和Node.js(MERN)一样。它是一个全栈解决方案,用于构建使用 MongoDB 作为数据存储、Express.js 作为后端框架(该框架建立在 Node.js 之上)、Vue.js 作为前端 JavaScript 框架以及 Node.js 作为后端主要引擎的基于 Web 的应用程序。
本书面向对使用 MongoDB、Express.js、Vue.js 和 Node.js 构建全栈 JavaScript 应用程序感兴趣的 Web 开发者。它适合对 HTML、CSS 和 JavaScript 有基本知识的初学者和中级开发者。
MEVN 这个术语可能很新,但其中使用的技术并不新。这里介绍的唯一新技术是 Vue.js。Vue.js 是一个开源的 JavaScript 框架,其受欢迎程度正在迅速增长。Vue.js 的学习曲线并不陡峭,它也是 AngularJS 和 ReactJS 等其他 JavaScript 框架的激烈竞争对手。
现代 Web 应用程序需要快速且易于扩展。在过去,JavaScript 仅在需要添加一些正常 HTML 和 CSS 无法实现的视觉效果或动画时才用于 Web 应用程序。但今天,JavaScript 已经改变了。今天,JavaScript 几乎用于所有基于 Web 的应用程序,从小型到大型应用程序。当应用程序需要更快和更交互式时,会选择 JavaScript。
使用 JavaScript 作为唯一编程语言构建全栈应用程序有其自身的好处:
-
如果你刚开始学习编程,你只需要掌握一种语言:JavaScript。
-
全栈工程师需求量大。成为一名全栈开发者意味着你了解数据库的工作原理,你知道如何构建后端和前端,并且你还具备 UI/UX 的技能。
在本书中,我们将使用这些技术栈来构建应用程序。
本章我们将涵盖以下主题:
-
MEVN 技术栈简介
-
Node.js 及其在 Windows、Linux 和 macOS 上的安装简介
-
npm及其安装概述 -
MongoDB 及其安装简介以及 MongoDB 中的一些基本命令
-
GitHub 版本控制简介及其如何帮助软件工程师轻松访问代码历史和协作
JavaScript 技术栈的演变
JavaScript 是当今最重要的编程语言之一。由 Brendan Eich 于 1995 年创立,它做得非常出色,不仅保持了其地位,而且还在所有其他编程语言之上脱颖而出。
JavaScript 的流行度一直在增长,而且似乎没有尽头。以 JavaScript 作为唯一编程语言构建 Web 应用程序一直很受欢迎。随着这种快速的增长速度,软件工程师对 JavaScript 的知识需求也在不断增加。无论你选择哪种编程语言来精通,JavaScript 总是以某种方式渗透进来,与其他编程语言一起参与,无论方式如何。
在开发应用程序时,前端和后端有很多技术可供选择。虽然这本书使用 Express.js 作为后端框架,但还有其他框架可供学习,如果你愿意的话。
其他可用的后端框架包括 Meteor.js、Sails.js、Hapi.js、Mojito、Koa.js 以及更多。
类似地,对于前端,技术包括 Vue.js、React、Angular、Backbone 以及更多。
对于数据库,除了 MongoDB 之外,还有 MySQL、PostgreSQL、Cassandra 以及其他选项。
介绍 MEVN
JavaScript 框架正日益增多,无论是数量还是使用频率都在上升。JavaScript 最初仅用于客户端逻辑实现,但经过多年的发展,现在它既用于前端也用于后端。
在 MEVN 堆栈中,Express.js 用于管理所有与后端相关的内容,Vue.js 处理所有与视图相关的内容。使用 MEVN 堆栈的优势如下:
-
整个应用程序中只使用一种语言,这意味着你只需要了解 JavaScript。
-
使用一种语言理解客户端和服务器端非常简单。
-
这是一个非常快速且可靠的应用程序,具有 Node.js 的非阻塞 I/O。
-
这是一种很好的方式,可以保持对 JavaScript 不断增长的生态系统的了解。
安装 Node.js
要开始,我们需要添加 MEVN 堆栈应用程序所需的全部依赖项。我们还可以参考官方网站(nodejs.org/)上的文档,了解如何在任何操作系统上安装 Node.js 的详细信息。
在 macOS 上安装 Node.js
在 macOS 上安装 Node.js 有两种方式:使用安装程序或使用 bash。
使用安装程序安装 Node.js
要使用安装程序安装 Node.js,请执行以下步骤:
- 安装安装程序:我们可以从官方网站的下载页面(
nodejs.org/en/#download)下载 macOS 的安装程序。我们将安装最新的node版本,即10.0.0。你可以安装任何你想要的node版本,但我们在本书中构建的应用程序将需要node版本>= 6.0.0。运行安装程序并遵循给出的说明。当我们下载并运行安装程序时,我们将看到一个如下对话框:
- 点击继续,直到安装完成。一旦安装完成,我们将能够看到以下对话框:
只需点击关闭,我们就会完成。
使用 bash 安装 Node.js
Node.js 可以很容易地使用 macOS 中的 Homebrew 安装。Homebrew 是一个免费的开源软件包管理器,用于在 macOS 上安装软件。我个人更喜欢 Homebrew,因为它使得在 Mac 上安装不同的软件变得非常容易:
- 要安装
Homebrew,请输入以下命令:
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- 现在,使用以下命令通过
Homebrew安装 Node.js:
$ brew install node
在 Linux 上安装 Node.js
对于 Linux,我们可以安装 Node.js 的默认发行版,或者我们可以从 NodeSource 下载它以使用最新版本。
从默认发行版安装 Node.js
要从默认发行版安装,我们可以使用以下命令在 Linux 上安装 Node.js:
$ sudo apt-get install -y nodejs
从 NodeSource 安装 Node.js
要从 NodeSource 安装 Node.js,请执行以下步骤:
- 首先从 NodeSource 下载最新版本的 Node.js:
$ curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash
- 然后,使用以下命令安装 Node.js:
$ sudo apt-get install -y nodejs
apt是高级包工具的简称,用于在 Debian 和 Linux 发行版上安装软件。基本上,这相当于 macOS 中的 Homebrew 命令。
在 Windows 上安装 Node.js
我们可以通过以下步骤在 Windows 上安装 Node.js:
-
从官方网站下载 Node.js 安装程序(
nodejs.org/en/download/)。 -
运行安装程序并按照给定的说明操作。
-
点击关闭/完成按钮。
通过安装程序在 Windows 上安装 Node.js 几乎与在 macOS 上相同。一旦我们下载并运行安装程序,我们将看到一个对话框。只需点击继续,直到安装完成。当我们最终看到一个带有确认的对话框时,我们点击关闭。Node.js 将被安装!
介绍 NVM
NVM代表Node 版本管理器。NVM 跟踪我们安装的所有node版本,并允许我们在不同版本之间切换。当我们为 Node.js 的一个版本构建的应用程序与另一个版本不兼容时,我们需要那个特定的node版本来使事情正常工作,这非常有用。NVM 使我们能够轻松管理这些版本。当我们需要升级或降级node版本时,这也非常有帮助。
从 NVM 安装 Node.js
- 要下载 NVM,请使用以下命令:
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
- 我们也可以使用以下命令:
$ wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash
- 使用以下命令检查
nvm是否已成功安装:
$ nvm --version
- 现在,要使用
nvm安装node,请使用以下命令:
$ nvm install node
介绍 npm
npm 是 Node Package Manager 的缩写。基本上,它是一个负责我们为 Node.js 安装的所有包的工具。我们可以在官方网站 (www.npmjs.com/) 上找到所有现有的包。npm 使开发者能够轻松地保持他们的代码更新,并重用许多其他开发者共享的代码。
开发者经常被包和模块这两个术语所困惑。然而,这两个术语之间有一个清晰的区分。
模块
模块是可以通过 Node.js 的 require 命令加载并具有命名空间的东西。一个模块会关联一个 package.json 文件。
包
一个 package 只是一个文件,或者一组文件,它能够独立运行。每个包也都有一个 package.json 文件,其中包含描述该包的所有相关元数据信息。模块的组合构成了一个 node 包。
安装 npm
当我们从安装程序本身安装 Node.js 时,npm 作为 node 的一部分被安装。我们可以通过使用以下命令来检查 npm 是否已安装:
$ npm --version
如果 npm 未安装,命令会显示错误,而如果已安装,它只会打印出已安装的 npm 版本。
使用 npm
npm 用于在我们的应用程序中安装不同的包。安装包有两种方式:本地和全局。当我们想要安装针对我们应用程序的特定包时,我们希望将其本地安装。然而,如果我们想要将某个包用作命令行工具或能够在我们应用程序外部访问它,我们则希望将其作为全局包安装。
本地安装 npm 包
要仅安装针对我们应用程序的特定包,我们可以使用以下命令:
$ npm install <package_name> --save
全局安装 npm 包
要全局安装包,我们可以使用以下命令:
$ npm install -g <package_name>
介绍 package.json
所有的 node 包和模块都包含一个名为 package.json 的文件。这个文件的主要功能是携带与该包或模块相关的所有元信息。一个 package.json 文件要求内容是一个 JSON 对象。
至少,一个 package.json 文件包含以下内容:
-
name:包的名称。这是
package.json文件的一个重要部分,因为它主要是区分其他包的东西,因此是一个必填字段。 -
version:包的版本。这也是一个必填字段。为了能够安装我们的包,需要提供
name和version字段。 -
description:包的简要总结。
-
main:这是用于查找包的主要入口点。基本上,它是一个文件路径,因此当用户安装此包时,它会知道从哪里开始查找模块。
-
脚本:此字段包含可以在应用程序的各种状态下运行的命令。它由键值对组成。
key是命令应该运行的触发事件,而value是实际的命令。 -
作者/贡献者:作者和贡献者是人们。它包含一个个人标识符。作者是一个单独的人,而贡献者可以是一组人。
-
许可协议:当提供许可字段时,使用户能够轻松使用我们的包。这有助于在使用包时识别权限和限制。
创建一个 package.json 文件
我们可以手动创建一个 package.json 文件并自行指定选项,或者我们可以使用命令从命令提示符交互式地创建它。
让我们继续使用 npm 初始化一个带有 package.json 的示例应用程序。
首先,使用以下命令在项目目录中创建一个文件夹:
$ mkdir testproject
要创建一个 package.json 文件,请在我们创建的应用程序中运行以下命令:
$ npm init
运行此命令将向我们提出一系列问题,我们可以通过命令行交互式地回答这些问题:
最后,它将创建一个 package.json 文件,其中将包含以下内容:
安装 MongoDB
MongoDB 是 MEVN 技术栈中的第一个部分。MongoDB 是一个在 GNU 许可下发布的免费和开源文档型数据库。它是一个 NoSQL 数据库,这意味着它是一个非关系型数据库。与使用表和行来表示数据的关系型数据库不同,MongoDB 使用集合和文档。MongoDB 将数据表示为 JSON 文档的集合。它为我们提供了以任何我们想要的方式添加字段的灵活性。单个集合中的每个文档都可以有完全不同的结构。除了添加字段外,它还提供了以任何我们想要的方式从文档到文档更改字段的灵活性,这在关系型数据库中是一个繁琐的任务。
与关系型数据库管理系统(RDBMS)相比,MongoDB 的优势
与关系型数据库管理系统(RDBMS)相比,MongoDB 提供了许多好处:
-
无模式架构:MongoDB 不要求我们为其集合设计特定的模式。一个文档的模式可以不同,另一个文档则可能完全不同。
-
每个文档都存储在 JSON 结构化格式中。
-
查询和索引 MongoDB 非常容易。
-
MongoDB 是一个免费和开源的程序。
在 macOS 上安装 MongoDB
安装 MongoDB 有两种方式。我们可以从官方 MongoDB 网站下载它(www.mongodb.org/downloads#production),或者我们可以使用 Homebrew 来安装它。
通过下载安装 MongoDB
-
从
www.mongodb.com/download-center#production下载您想要的 MongoDB 版本。 -
将下载的 gzipped 文件复制到根目录。将其添加到根目录将允许我们全局使用它:
$ cd Downloads $ mv mongodb-osx-x86_64-3.0.7.tgz ~/
- 解压 gzipped 文件:
$ tar -zxvf mongodb-osx-x86_64-3.0.7.tgz
- 创建一个目录,该目录将由 Mongo 用于保存数据:
$ mkdir -p /data/db
- 现在,为了检查安装是否成功,启动 Mongo 服务器:
$ ~/mongodb/bin/mongod
在这里,我们已经成功安装并启动了 mongo 服务器。
通过 Homebrew 安装 MongoDB
要在 macOS 上使用 Homebrew 安装 MongoDB,请按照以下步骤操作:
- 使用 Homebrew,我们只需要一个命令来安装 MongoDB:
$ brew install mongodb
- 创建一个目录,该目录将由 Mongo 用于保存数据:
$ sudo mkdir -p /data/db
- 启动 Mongo 服务器:
$ ~/mongodb/bin/mongod
因此,MongoDB 最终已安装。
在 Linux 上安装 MongoDB
在 Linux 上安装 MongoDB 也有两种方式:我们可以使用 apt-get 命令,或者我们可以下载 tarball 并解压它。
使用 apt-get 安装 MongoDB
要使用 apt-get 安装 MongoDB,请执行以下步骤:
- 运行以下命令以安装 MongoDB 的最新版本:
$ sudo apt-get install -y mongodb-org
- 通过运行以下命令来验证
mongod是否已成功安装:
$ cd /var/log/mongodb/mongod.log
- 要启动
mongod进程,请在终端中执行以下命令:
$ sudo service mongod start
- 查看日志文件是否有表示 MongoDB 连接成功建立的行:
$ [initandlisten] waiting for connections on port<port>
- 要停止
mongod进程:
$ sudo service mongod stop
- 要重新启动
mongod进程:
$ sudo service mongod restart
使用 tarball 安装 MongoDB
- 从
www.mongodb.com/download-center?_ga=2.230171226.752000573.1511359743-2029118384.1508567417下载二进制文件。使用以下命令:
$ curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-
3.4.10.tgz
- 解压下载的文件:
$ tar -zxvf mongodb-linux-x86_64-3.4.10.tgz
- 复制并解压到目标目录:
$ mkdir -p mongodb $ cp -R -n mongodb-linux-x86_64-3.4.10/ mongodb
- 在 PATH 变量中设置二进制文件的位置:
$ export PATH=<mongodb-install-directory>/bin:$PATH
- 创建一个目录,供 Mongo 用于存储所有数据库相关数据:
$ mkdir -p /data/db
- 要启动
mongod进程:
$ mongod
在 Windows 上安装 MongoDB
从安装程序安装 MongoDB 与在 Windows 上安装任何其他软件一样简单。就像我们为 Node.js 所做的那样,我们可以从官方网站 (www.mongodb.com/download-center#atlas) 下载 MongoDB 安装程序。这将下载一个可执行文件。
一旦可执行文件下载完成,运行安装程序并按照说明操作。只需浏览对话框,仔细阅读说明。安装完成后,只需点击“关闭”按钮即可完成。
使用 MongoDB
让我们更深入地了解 MongoDB。正如之前提到的,Mongo 包含一个数据库,其中包含集合(表格/数据组)和文档(行/条目/记录)。我们将使用 MongoDB 提供的一些命令来创建、更新和删除文档:
首先,使用以下命令启动 Mongo 服务器:
$ mongod
然后,使用以下命令打开 Mongo shell:
$ mongo
创建或使用 MongoDB 数据库
这是我们可以看到所有数据库、集合和文档的地方。
要显示我们拥有的数据库列表,我们可以使用以下命令:
> show dbs
现在,此命令应该列出所有现有数据库。要使用我们想要的数据库,我们可以简单地运行此命令:
> use <database_name>
但是,如果没有列出数据库,请不要担心。MongoDB 为我们提供了一个功能,当我们运行前面的命令时,即使该数据库不存在,它也会自动为我们创建一个具有给定名称的数据库。
因此,如果我们已经有一个想要使用的数据库,我们只需运行该命令,如果没有数据库,我们将使用此命令创建一个:
> use posts
当我们运行此命令时,将创建一个名为posts的数据库。
创建文档
现在,让我们快速回顾一下在 MongoDB 中使用的命令。insert命令用于在 MongoDB 中的集合中创建新文档。让我们向刚刚创建的名为posts的数据库中添加一条新记录。
在这里也是如此,为了向集合中添加文档,我们首先需要一个集合,我们还没有。但是 MongoDB 允许我们通过运行insert命令轻松创建集合。同样,如果集合存在,它将文档添加到指定的集合中,如果集合不存在,它将简单地创建一个新的集合。
现在,在 Mongo shell 中,运行以下命令:
> db.posts.insertOne({
title: 'MEVN',
description: 'Yet another Javascript full stack technology'
});
此命令将在posts数据库中创建一个名为posts的新集合。此命令的输出是:
它将返回一个 JSON 对象,其中包含我们在insertedId键中创建的文档的 ID,以及事件被接收为acknowledged的标志。
获取文档
此命令用于当我们想要从一个集合中获取记录时。我们可以通过传递参数来获取所有记录或特定文档。我们可以向posts数据库添加一些更多文档,以便更好地学习该命令
获取所有文档
要获取posts集合中的所有记录,请运行以下命令:
> db.posts.find()
这将返回posts集合中我们拥有的所有文档:
获取特定文档
让我们找到一个标题为MEVN的帖子。为此,我们可以运行:
> db.posts.find({ 'title': 'MEVN' })
此命令将只返回标题为MEVN的文档:
更新文档
此命令用于当我们想要更新集合的某个部分时。比如说,我们想要更新标题为Vue.js的帖子的描述;我们可以运行以下命令:
> db.posts.updateOne(
{ "title" : "MEVN" },
{ $set: { "description" : "A frontend framework for Javascript programming language" } }
)
此命令的输出将是:
我们可以看到这里matchedCount是1,这意味着在posts集合中有一个文档与我们要更新的标题MEVN的查询参数相匹配。
另一个关键值modifiedCount给出了更新文档的数量。
删除文档
delete命令用于从集合中删除文档。从 MongoDB 中删除文档有几种方法。
删除符合给定条件的文档
要删除具有特定条件的所有文档,我们可以运行:
> db.posts.remove({ title: 'MEVN' })
此命令将从posts集合中删除所有标题为MEVN的文档。
删除符合给定条件的单个文档
要删除仅满足给定条件的第一个记录,我们只需使用:
> db.posts.deleteOne({ title: 'Expressjs' })
删除所有记录
要删除集合中的所有记录,我们可以使用:
> db.posts.remove({})
介绍 Git
Git 是我们应用程序中跟踪代码变更的版本控制系统。它是一个免费的开源软件,用于在构建应用程序时跟踪和协调多个用户。
要开始使用此软件,我们首先需要安装它。在每种操作系统上安装它都非常简单。
在 Windows 上安装 Git
我们可以在gitforwindows.org/找到 Git for Windows 的安装程序。
下载 Windows 的可执行安装文件并按照相应的步骤进行操作。
在 Mac 上安装 Git
我们可以轻松通过 Homebrew 在 Mac 上安装 Git。只需在命令行中输入以下命令即可在 Mac 上安装 Git:
$ brew install git
在 Linux 上安装 Git
在 Linux 上安装 Git 与在 macOS 上安装 Git 一样简单。只需在命令行中输入以下命令并按回车键即可在 Linux 上安装 Git:
$ sudo apt-get install git
介绍 GitHub
GitHub 是一个版本控制系统。它是一个专门设计用来跟踪代码变更的源代码管理工具。GitHub 还提供了社交网络功能,例如添加评论和显示动态,这使得它更加强大,因为多个开发者可以在单个应用程序中同时协作。
为什么选择 GitHub?
GitHub 是软件工程师的救星。GitHub 提供了许多优势,使其值得使用。以下是 GitHub 提供的一些好处:
-
跟踪代码变更:GitHub 帮助跟踪代码变更,这意味着它维护了我们代码的历史记录。这使得我们能够查看在任何时间段内对代码库进行的修订。
-
文档:GitHub 为我们提供了添加文档、维基等功能,这些可以使用简单的 Markdown 语言编写。
-
图表和报告:GitHub 提供了对各种指标的洞察,包括对代码进行了多少次添加和删除,谁是主要贡献者,以及谁提交最多。
-
错误跟踪:由于 GitHub 跟踪每个时间点的所有活动,当出现问题时,我们可以轻松回溯到导致代码中断的点。我们还可以集成第三方工具,如 Travis 进行持续集成,这有助于我们轻松跟踪和识别错误。
-
协作简单:GitHub 跟踪项目中每个协作者的所有活动,并为此发送电子邮件通知。它还提供社交媒体功能,如动态、评论、表情符号和提及。
-
托管自己的网站:我们还可以使用 GitHub 的 GitHub Pages 功能托管自己的网站。我们只需要为我们的项目创建一个仓库,并使用 GitHub Pages 托管它,这样网站就会适用于 URL:
https://<username>.github.io。
使用 GitHub
GitHub 非常易于使用。然而,要开始使用 GitHub,我们至少需要了解一些在 GitHub 中使用的术语:
-
仓库/Repo:仓库是我们所有代码库存储的地方。仓库可以是私有的或公共的。
-
ssh-key:ssh-key 是 GitHub 中授权的一种方式。它存储我们的身份信息。
-
分支:分支可以被定义为仓库的多个状态。任何仓库的主要分支是
master分支。多个用户可以在不同的分支上并行工作。 -
提交:提交使得在给定时间区分文件的不同状态变得容易。当我们提交时,会为该提交分配一个唯一的标识符,这样我们就可以轻松检查在该提交中做了哪些更改。提交以消息作为参数,描述正在进行的更改类型。
-
推送:推送将我们做出的提交发送回我们的仓库。
-
拉取:与推送相反,拉取是从远程仓库将提交拉取到我们的本地项目。
-
合并:合并基本上是在多个分支之间进行的。它用于将一个分支的更改应用到另一个分支。
-
拉取请求:创建一个
pull request基本上是将我们对我们代码库所做的更改发送给其他开发者的审批。我们可以在pull request上开始讨论,以检查代码质量并确保更改不会破坏任何东西。
要了解更多关于 GitHub 中使用的词汇,请访问help.github.com/articles/github-glossary/。
设置 GitHub 仓库
现在我们已经了解了 GitHub 的基础知识,让我们开始创建我们想要构建的项目 GitHub 仓库:
- 首先,在根目录下为应用程序创建一个文件夹。让我们把这个应用程序命名为
blog:
$ mkdir blog
-
在
github.com/上创建一个 GitHub 账户。 -
前往您的个人资料。在“仓库”选项卡下,点击“新建”,如下所示:
-
将此仓库命名为
blog。 -
现在,在终端中,转到此应用程序的位置,并使用以下命令初始化一个空的仓库:
$ cd blog $ git init
- 现在,让我们创建一个名为
README.md的文件,为应用程序编写描述,然后保存它:
$ echo 'Blog' > README.md
- 将此文件添加到 GitHub:
$ git add README.md
- 添加一个
提交,以便我们有这个代码更改的历史记录:
$ git commit -m 'Initial Commit'
- 现在,为了将本地应用程序与 GitHub 中的
远程仓库链接起来,请使用以下命令:
$ git remote add origin https://github.com/{github_username}/blog.git
- 最后,我们需要将这个
提交``push到 GitHub:
$ git push -u origin master
完成后,访问 GitHub 仓库,在那里你可以找到对我们仓库所做的提交历史,如下所示:
就这样。现在,当我们想要编写更改时,我们首先会创建一个分支,然后将更改push到该分支。
摘要
在本章中,我们学习了 MEVN 栈是什么。我们还了解了 Node.js、npm 和 MongoDB 是什么,以及 GitHub 的简要概述,以及它是如何帮助软件工程师方便地访问代码历史和协作的。
在下一章中,我们将学习更多关于 Node.js 和 Node.js 模块的知识。我们将了解 MVC 架构以及如何通过使用 Express.js 构建应用程序来实现它。
第二章:构建 Express 应用
Express.js 是一个 Node.js 网络应用框架。Express.js 使得使用 Node.js 更加容易,并利用了其强大功能。在本章中,我们将仅使用 Express.js 创建一个应用。Express.js 也是一个node包。我们可以使用应用程序生成器工具,它让我们可以轻松地创建 Express 应用的骨架,或者我们可以从头开始自己创建。
在上一章中,我们学习了npm是什么,包是什么,以及如何安装一个包。在本章中,我们将涵盖以下元素:
-
Node.js 是什么以及它能做什么
-
它带来的好处
-
Node.js 的基本编程
-
Node.js 核心和自定义模块
-
Express.js 简介
-
使用 Express.js 创建应用
-
Express.js 中的路由
-
MVC 架构:它是什么以及它在应用中实现时增加了什么价值
-
应用的文件命名规范
-
对文件夹进行重组以融入 MVC 模式
-
为 Express.js 应用创建视图
有很多npm包可以帮助我们创建 Express.js 应用的骨架。其中一个这样的包是express-generator。它让我们能在几秒钟内搭建整个应用。它将以模块化结构创建所有必要的文件和文件夹。它生成的文件结构非常易于理解。我们唯一需要做的是定义模板视图和路由。
我们也可以根据我们的需求和需求修改这个结构。当我们面临紧迫的截止日期,想在一天或几天内构建一个应用时,这非常方便。这个过程极其简单。
express-generator只是众多可用于创建 Express 应用骨架或模块化结构的工具之一。每个生成器工具都有其自己的文件结构构建方式,这可以根据其标准轻松定制。
如果你是一个初学者并且想了解文件夹结构是如何工作的,我建议你从头开始构建应用。我们将在本章中进一步讨论这一点。
要开始,我们首先需要了解 Node.js,然后再深入研究 Express.js。
Node.js 简介
Node.js 是一个基于 JavaScript 引擎构建的 JavaScript 运行时。它是一个开源框架,用于服务器端管理。Node.js 轻量级且高效,可在 Windows、Linux 和 macOS 等平台上运行。
Node.js 由 Ryan Dahl 于 2009 年创建。JavaScript 过去主要用于客户端脚本,但 Node.js 使得 JavaScript 也可以在服务器端使用。Node.js 的发明引入了在 Web 应用中使用单一编程语言的做法。Node.js 带来了很多好处,其中一些如下:
-
事件驱动编程:这意味着改变一个对象的状态从一个变为另一个。Node.js 使用事件驱动编程,这意味着它使用用户的交互动作,如鼠标点击和按键,来改变对象的状态。
-
非阻塞 I/O:非阻塞 I/O 或非同步 I/O 意味着异步 I/O。同步进程会等待当前运行进程完成,因此会阻塞进程。另一方面,异步进程不需要等待该进程完成,这使得它既快速又可靠。
-
单线程:单线程意味着 JavaScript 只在单个事件循环中运行。由于异步进程允许我们同时拥有多个进程,这可能会让人感觉所有这些进程都在它们自己的特定线程中运行。但 Node.js 处理异步的方式略有不同。Node.js 中的事件循环在相应事件发生后触发下一个已安排执行的回调函数。
理解 Node.js
在深入研究 Node.js 编程之前,让我们先了解一下 Node.js 的一些基础知识。Node.js 运行在 JavaScript V8 引擎上。JavaScript V8 引擎是由Chromium 项目为 Google Chrome 和 Chromium 网络浏览器构建的。这是一个用 C++编写的开源项目。这个引擎用于客户端和服务器端 Web 应用程序的 JavaScript。
Node.js 编程
让我们从运行一个node进程开始。打开终端并输入以下命令:
$ node
这将启动一个新的node进程。我们在这里可以编写正常的 JavaScript 代码。
例如,我们可以在新的 Node shell 中编写以下 JavaScript 命令:
> var a = 1;
当我们输入a并按回车键时,它返回1。
我们也可以在node进程中运行扩展名为.js的文件。让我们在根目录下使用命令mkdir tutorial创建一个名为tutorial的文件夹,并在其中创建一个名为tutorial.js的文件。
现在,在终端中,让我们使用以下命令进入该目录:
$ cd tutorial $ node tutorial.js
我们应该看到以下类似的内容:
由于我们还没有为tutorial.js编写任何内容,所以它不会返回任何内容。
现在,让我们向tutorial.js文件中添加一些代码:
console.log('Hello World');
现在,使用以下命令运行文件:
$ node tutorial.js
我们将看到一个输出显示Hello World。这就是我们在 Node.js 中执行文件的方式。
除了在 V8 引擎上运行并在浏览器中执行 JavaScript 代码之外,Node.js 还提供了一个服务器运行环境。这是 Node.js 最强大的功能。Node.js 提供了一个自带的 HTTP 模块,它实现了一个非阻塞的 HTTP 实现。让我们构建一个简单的 Web 服务器来理解这一点。
在同一文件tutorial.js中,用以下代码覆盖文件:
const http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n');
}).listen(8080, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8080/');
在这里,var http = require('http');这段代码将 HTTP 模块引入到我们的应用程序中。这意味着现在我们可以通过http变量访问 HTTP 库中定义的函数。现在我们需要创建一个 Web 服务器。前面的代码告诉 Node.js 在 8080 端口运行 Web 服务器。createServer方法中的function参数接受两个参数,分别是req和res,它们分别是请求和响应的简称。在这个函数内部,我们首先需要做的是设置 HTTP 头。这基本上是定义我们希望从该请求中获取哪种类型的响应。然后,我们使用res.send定义我们希望在响应中获取的内容。最后,我们要求 Web 服务器监听 8080 端口。
当我们使用$ node tutorial.js运行此代码时,输出看起来像这样:
当我们在浏览器中输入该 URL 时,我们应该能够看到以下内容:
这就是 Node.js 作为服务器程序的工作方式。
要退出node控制台,请按Ctrl + C两次。
Node.js 模块
Node.js 模块只是一个普通的 JavaScript 文件,它包含可重用的代码。每个模块都有自己的特定功能。我们可以将其视为一个库。
例如,如果我们想在应用程序中隔离所有与用户相关的活动,我们可以为它创建一个模块,该模块将处理所有关于用户的数据库。
在 Node.js 中使用模块的方式是通过require。我们刚才向您展示的创建 Web 服务器的例子也是一个 Node.js 模块。
Node.js 核心模块
在 Node.js 中有两种类型的模块。核心模块是在 Node.js 中构建的模块。在我们安装 Node.js 时它们就存在了。这些也被称为内置模块。Node.js 中有许多核心模块:
-
调试器
-
文件系统
-
HTTP
-
路径
-
处理
-
事件
如果你想详细了解每个核心模块的更多细节,你可以访问以下文档:
自定义模块
这些是我们自己基于 Node.js 创建的模块。由于 Node.js 有一个非常大的生态系统,因此有大量的不同模块可供我们根据需要免费获取。我们可以自己构建一个,或者直接使用别人的模块。这也是 Node.js 强大之处的一个方面。它为我们提供了使用社区模块或自己构建模块的灵活性。
我们可以在www.npmjs.com/browse/depended查看所有现有可用模块的列表:
介绍 Express.js
Express.js 是一个用于 Node.js 的极简服务器端网络框架。它建立在 Node.js 之上,以便更容易地管理 Node.js 服务器。Express.js 最重要的优势是它使路由变得非常简单。它提供的强大 API 非常容易配置。从前端接收请求和连接到数据库都很简单。Express.js 也是 Node.js 最受欢迎的网络框架。它使用 模型-视图-控制器(MVC)设计模式,我们将在本章后面讨论。
安装 Express.js
我们已经介绍了如何通过 npm 安装 node 模块。同样,我们可以使用此命令通过 NPM 安装 Express.js:
$ npm install express
这是一种安装 node 模块的简单方法。但是,在构建应用程序时,我们需要很多不同种类的模块。我们还想在多个应用程序之间共享这些模块。因此,为了使模块全局可用,我们必须全局安装它。为此,npm 提供了在安装 node 模块时添加 -g 选项的功能。所以,现在我们可以使用:
$ npm install -g express
这将全局安装 Express.js,这允许我们在多个应用程序中使用 express 命令。
创建 Express.js 应用程序
现在我们已经安装了 Express.js,让我们开始使用 Express.js 创建应用程序。
我们将命名我们的应用程序为 express_app。使用 express 命令构建 express 应用的轮廓非常简单。我们可以简单地使用:
$ express express_app
输出如下所示:
此命令会在我们的应用程序中创建大量的文件和文件夹。让我们快速看一下这些:
-
package.json:此文件包含我们在应用程序中安装的所有node包的列表以及应用程序的简介。 -
app.js:此文件是 express 应用的主入口页面。网络服务器代码位于此文件中。 -
public:我们可以使用此文件夹来插入我们的资产,例如图片、样式表或自定义 JavaScript 代码。 -
views:此文件夹包含所有将在浏览器中渲染的视图文件。它包含主布局文件(其中包含视图文件的 HTML 模板),一个index.jade文件(它扩展了布局文件,只包含可变或动态的内容),以及一个error.jade文件(当我们需要向前端显示某种错误消息时显示)。 -
routes:此文件夹包含我们将构建的所有路由,以便访问应用程序的不同页面。我们将在后续章节中进一步讨论。 -
bin:此文件夹包含 Node.js 的可执行文件。
因此,这些都是我们需要了解的基本知识。现在,使用您喜欢的文本编辑器来处理应用程序,让我们开始吧。现在,如果我们查看 package.json,会发现某些我们没有安装但列在依赖项中的包:
这是因为这些是 Express.js 任何应用程序的依赖项。这意味着,当我们使用 express 命令创建应用程序时,它将自动安装它需要的所有依赖项。例如,前面 package.json 文件中列出的依赖项执行以下操作:
-
body-parser:用于解析我们在发起 HTTP 请求时提供的请求体参数
-
debug:这是一个提供
console.log返回值的格式化工具的 JavaScript 实用程序包我们可以通过
package.json文件安装或删除包。只需在package.json文件中添加或删除包的名称,然后运行$ npm install。 -
express:这是一个 Node.js JavaScript 框架,用于在 Node.js 上构建可扩展的 Web 应用程序。
-
jade:如前所述,这是 Node.js 的默认模板引擎。我们应该在用
express命令创建应用程序时看到一条警告信息,说明在未来的版本中默认视图引擎将不再是 jade。这是因为jade被一家公司拥有版权,后来将其名称更改为pug。
Express 生成器使用过时的 jade 模板引擎。要更改模板引擎,请按照以下步骤操作:
- 在
package.json文件中,删除"jade": "~1.11.0"行并运行:
$ cd express_app
$ npm install
- 现在,要安装新的
pug模板引擎,请运行:
$ npm install pug --save
-
如果我们查看
package.json文件,我们应该看到一条类似以下内容的行:"pug": "².0.0-rc.4"。 -
重命名
views文件夹中的文件:-
将
error.jade重命名为error.pug -
将
index.jade重命名为index.pug -
将
layout.jade重命名为layout.pug
-
-
最后,在
app.js中,删除显示以下内容的行:
app.set('view engine', 'jade');
- 添加以下行以使用
pug作为视图引擎:
app.set('view engine', 'pug');
-
morgan:这是一个用于记录 HTTP 请求的中间件
-
serve-favicon:这是在浏览器中显示 favicon 以识别我们的应用程序
对于我们的应用程序来说,没有必要拥有所有这些依赖项。它们来自安装 Express.js。只需查找你想要的,然后根据应用程序的需求添加或删除包。
目前,我们将保持原样。express 命令只是将依赖项添加到我们的 package.json 文件中,并为我们的应用程序创建一个骨架。为了实际安装 package.json 文件中列出的这些模块和包,我们需要运行:
$ npm install
这个命令实际上会安装所有依赖项。现在,如果我们查看文件夹结构,我们可以看到一个名为 node_modules 的新文件夹被添加。这就是我们在这个应用程序中安装的所有包的存放地。
现在,我们首先想要做的是设置一个 Web 服务器。为此,在 app.js 文件中添加以下行:
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
app.listen(3000, function() { console.log('listening on 3000') })
module.exports = app;
现在,运行以下命令:
$ node app.js
这将启动我们的应用程序服务器。现在,当我们访问 http://localhost:3000/ URL 时,我们应该能够得到以下内容:
就这些。我们已经成功创建了一个 Express 应用程序。
Express 路由器
让我们继续到 Express 路由器。如本章前面所述,Express.js 最重要的一点是它为应用程序提供了简单的路由。路由是应用程序 URL 的定义。如果我们查看 app.js,我们会看到一个类似以下的部分:
...
app.use('/', index);
app.use('/users', users);
...
这意味着当我们访问一个网页,并且当对主页发起请求时,Express 路由器将其重定向到名为 index 的路由器。现在,看看 routes/index.js 文件,它包含以下代码:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
这意味着当我们访问主页时,它会渲染一个名为 index 的页面,该页面位于 views/index.pug 内,并将要显示在页面上的 title 参数传递过去。现在,看看位于视图文件夹中的 index.pug 文件,它包含以下代码:
extends layout
block content
h1= title
p Welcome to #{title}
这意味着它使用了 layout.pug 文件中的布局,并显示了一个 h1 标题以及一个渲染我们从前端文件传递过来的标题的段落。因此,输出如下:
非常简单直接,对吧?
请求对象
请求对象是一个包含 HTTP 请求信息的对象。请求的属性包括:
-
query: 这包含有关解析查询字符串的信息。通过
req.query访问。 -
params: 这包含有关解析路由参数的信息。通过
req.params访问。 -
body: 这包含有关解析请求体的信息。通过
req.body访问。
响应对象
在 req 变量上接收到 request 之后,res 对象是我们想要发送回的 response。
响应对象的属性包括:
-
send: 这用于向视图发送响应。通过
res.send访问。它接受两个参数,状态码和响应体。 -
status: 如果我们想要发送应用程序的成功或失败,使用
res.status。这是 HTTP 状态码。 -
redirect: 当我们想要重定向到某个页面而不是以其他格式发送响应时,使用
res.redirect。
MVC 简介
MVC 模型在构建应用程序时至关重要,无论使用哪种编程语言。MVC 架构使得组织我们应用程序的结构和分离逻辑部分和视图部分变得容易。我们可以在任何时间点整合这个 MVC 结构,即使我们已经完成了应用程序的一半。最佳实施时间是任何应用程序的开始。
如其名所示,它有三个部分:
-
Model: 所有应用程序的业务逻辑都位于这些
models之下。它们处理数据库。它们处理应用程序的所有逻辑部分。 -
**视图:**浏览器渲染的任何内容——用户看到的内容——都由这些视图文件处理。它处理我们发送给客户端的任何内容。
-
控制器:
Controllers基本上连接这些models和视图。它负责将models中完成的逻辑计算传递到views部分:
在我们构建的应用程序中实现 MVC 平台并不是必需的。JavaScript 是无模式的,这意味着我们可以创建自己的文件夹结构。与其他编程语言不同,我们可以选择对我们来说最容易的结构。
为什么使用 MVC?
当我们将 MVC 架构实现到我们的应用程序中时,会添加很多好处:
-
商业逻辑和视图的清晰分离。这种分离使我们能够在整个应用程序中重用业务逻辑。
-
开发过程变得更快。这一点很明显,因为各个部分已经清晰地分离出来。我们只需将视图添加到视图文件夹中,并在
models文件夹中添加逻辑。 -
修改现有代码很容易。当多个开发者共同参与同一项目时,这一点非常方便。任何人都可以从任何地方开始修改应用程序。
将文件夹结构修改为包含 MVC
既然我们已经对 MVC 有了足够的了解,让我们修改一下我们创建的应用程序的文件夹结构,该应用程序名为express_app。首先,我们需要在根目录中创建这三个文件夹。已经有一个views文件夹,所以我们可以跳过它。让我们继续创建models和controllers文件夹。
之后,在我们的app.js中,我们需要包含我们的控制器文件。为此,我们首先需要引入一个新的包,称为 filesystem。这个模块使得执行与文件相关的操作变得容易,例如读取/写入文件。
因此,要将这个包添加到我们的应用程序中,请运行:
$ npm install file-system --save
这个--save参数用于当我们想要一个node模块只在我们应用程序中安装时。此外,安装后,这个包将自动包含在我们的package.json中。
现在,我们需要引入这个模块并使用它来包含所有位于控制器中的文件。为此,在app.js中添加以下代码行。确保在运行我们的网络服务器代码之前添加这些行:
var index = require('./routes/index');
var users = require('./routes/users');
var app = express();
// Require file system module
var fs = require('file-system');
// Include controllers
fs.readdirSync('controllers').forEach(function (file) {
if(file.substr(-3) == '.js') {
const route = require('./controllers/' + file)
route.controller(app)
}
})
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
让我们继续添加一个路由到我们的控制器。让我们在应用程序的根目录中创建一个名为controllers的文件夹,并在controllers文件夹中添加一个index.js文件,并将以下代码粘贴进去:
module.exports.controller = (app) => {
// get homepage
app.get('/', (req, res) => {
res.render('index', { title: 'Express' });
})
}
现在,所有路由都将由控制器文件处理,这意味着我们不需要在app.js中控制路由的代码。因此,我们可以从文件中删除这些行:
var index = require('./routes/index');
var users = require('./routes/users');
app.use('/', index);
app.use('/users', users);
实际上,我们不再需要那个routes文件夹。让我们也删除routes文件夹。
同样,让我们添加一个新的路由来控制所有与用户相关的操作。为此,在 controllers 文件夹中添加一个名为 users.js 的新文件,并将以下代码粘贴到其中:
module.exports.controller = (app) => {
// get users page
app.get('/users', (req, res) => {
res.render('index', { title: 'Users' });
})
}
现在,让我们使用以下命令重新启动我们的应用节点服务器:
$ node app.js
通过这种方式,当我们访问 http://localhost:3000/users 时,我们将能够看到以下内容:
我们已经成功设置了 MVC 架构中的 controllers 和 views 部分。我们将在后续章节中进一步介绍 models 部分。
在上一章中,我们讨论了 GitHub 以及如何通过进行小提交来使用它来记录代码历史。不要忘记设置仓库并持续将代码推送到 GitHub。
npm 包存储在 node_modules 目录中,我们不应将其推送到 GitHub。为了忽略此类文件,我们可以添加一个名为 .gitignore 的文件,并指定我们不想推送到 GitHub 的文件。
让我们在我们的应用程序中创建一个名为 .gitignore 的文件,并添加以下内容:
node_modules/
这样,当我们安装任何包时,它不会在向 GitHub 提交代码时显示为代码差异。
每当我们对代码进行一些更改时,我们都需要重新启动我们的 node 服务器,这非常耗时。为了简化这个过程,node 提供了一个名为 nodemon 的包,它会在我们更改代码时自动重新启动服务器。
要安装该包,请运行:
$ npm install nodemon --save
要运行服务器,请使用以下命令:
$ nodemon app.js
文件命名规范
在开发应用程序时,我们需要遵循一定的命名约定来命名文件。随着我们继续构建应用程序,我们将拥有大量的文件,这可能会变得混乱。MVC 允许在不同的文件夹之间有并行的命名规范,这可能导致不同文件夹中存在相同的文件名。
如果我们认为这样做既简单又易于维护,我们也可以处理这样的文件名。否则,我们只需将文件类型追加到每个文件中,例如,在以下示例中;对于处理用户相关活动的控制器文件,我们可以将其保留为 controllers/users.js,或者我们可以将其重命名为 controllers/users_controller.js。我们将为我们的应用程序使用 controllers/users。
对于 models、services 或任何需要在应用程序的不同区域之间共享的文件夹也是如此。对于这个应用程序,我们将使用以下命名约定:
记住,Node.js 中没有官方的命名规范。我们绝对可以自定义我们找到的更简单的方式。我们将在后续章节中进一步讨论创建 models 的问题。这需要我们与 Mongo 建立连接,我们将在后续章节中描述。
为 Express.js 应用程序创建视图文件
在上一节中,我们学习了如何创建 controllers。在本节中,我们将讨论如何添加和自定义视图文件。如果您还记得,我们在 controllers/users.js 中有这段代码:
module.exports.controller = (app) => {
// get users page
app.get('/users', (req, res) => {
res.render('index', { title: 'Users' });
})
}
让我们更改渲染 index 文件的行,改为以下内容:
module.exports.controller = (app) => {
// get users page
app.get('/users', (req, res) => {
res.render('users', { title: 'Users' });
})
}
这意味着控制器想要加载 views 文件夹中的 users 文件。让我们继续在 views 文件夹中创建一个 users.pug 文件。
创建文件后,粘贴以下代码;这与我们在 views 文件夹中的 index.pug 文件中的代码相同:
extends layout
block content
h1= title
p Welcome to #{title}
现在,如果我们使用了 nodemon,我们不需要重新启动服务器;只需用位置 http://localhost:3000/users 重新加载浏览器。这应该会渲染以下内容:
现在我们已经知道了如何连接 controllers 和 views 以及如何创建视图文件,让我们来获取一些关于文件代码的更多信息。
第一行表示:
extends layout
这意味着它要求扩展 layout.pug 文件中已经存在的视图。现在,看看 layout.pug:
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
这是一个简单的 HTML 文件,包含 doctype、HTML、head 和 body 标签。在 body 标签内,它指示阻塞内容,这意味着它从任何其他文件中产生内容,这些文件都写在 block content 语句之下。如果我们查看 users.jade,我们可以看到内容是写在 block content 语句之下的。现在,这非常实用,因为我们不需要在创建的每个视图文件中重复整个 HTML 标签。
此外,如果我们查看控制器中的 users.js,有一行写着:
res.render('users', { title: 'Users' });
渲染方法有两个参数:它想要加载的视图以及想要传递给该视图的变量。在这个例子中,Users 被传递给标题变量。在 views 文件夹中的 users.jade 文件里,我们有:
block content
h1= title
p Welcome to #{title}
这会在 h1 标签和 p 标签内渲染该变量。这样,我们可以从 controllers 向视图传递任何我们想要的内容。让我们在 users.js 控制器的 render 方法中添加一个新的变量 description:
module.exports.controller = (app) => {
// get homepage
app.get('/users', (req, res) => {
res.render('users', { title: 'Users', description: 'This is the description of all the users' });
})
}
此外,让我们在 users.pug 中创建一个渲染该内容的区域:
extends layout
block content
h1= title
p Welcome to #{title}
p #{description}
如果我们重新加载浏览器,我们会得到:
这就是我们在 Express 应用程序中创建视图的方法。现在,继续添加您希望为我们的应用程序添加的视图。
总是要确保将更改提交并推送到 GitHub。提交越小,代码的可维护性就越高。
摘要
在这一章中,我们学习了 Node.js 是什么以及 Express.js 是什么。我们学习了如何使用 Express.js 创建应用程序,并了解了 MVC 架构。
在下一章中,我们将讨论 MongoDB 及其查询。我们还将讨论使用 Mongoose 进行快速开发和 Mongoose 查询和验证。
第三章:介绍 MongoDB
MongoDB 的名称来源于短语 huMONGOus data,意味着它可以处理大量数据。MongoDB 是一种面向文档的数据库架构。它使我们能够更快地开发并更好地扩展。在关系型数据库设计中,我们通过创建表和行来存储数据,但与 MongoDB 相比,我们可以将数据建模为 JSON 文档,这比那些关系型数据库要简单得多。如果我们是敏捷的,并且我们的需求经常变化,并且如果我们需要持续部署,那么 MongoDB 是我们的选择。作为一个基于文档的数据模型,MongoDB 也非常灵活。
使用 MongoDB 的最大优势是数据非结构化。我们可以以任何我们喜欢的格式自定义我们的数据。在关系型数据库管理系统(RDBMS)中,我们必须精确地定义一个表可以有多少字段,但与 MongoDB 不同,每个文档都可以有自己的字段数。我们可以添加新数据,甚至无需担心更改模式,这就是为什么 Mongo 数据库采用了无模式设计模型。
如果我们的业务增长迅速,我们需要更快地扩展,以更灵活的方式访问数据,如果我们需要更改数据而无需担心更新应用程序的数据库模式,那么 MongoDB 是我们最佳的选择。在 RDBMS 的表中添加新列也会引起一些性能问题。但是,由于 MongoDB 是无模式的,添加新字段可以瞬间完成,而不会影响我们应用程序的性能。
在关系型数据库中,我们使用的术语是数据库、表和行,而在 MongoDB 中,我们分别使用数据库、集合和文档。
本章将简要概述我们将要涵盖的内容:
-
介绍 MongoDB 及其使用 MongoDB 的好处
-
理解 MongoDB 数据库、集合和文档
-
介绍 Mongoose,使用 Mongoose 建立连接,理解 Mongoose,以及使用 Mongoose 进行 CRUD 操作
-
使用 Mongoose 添加默认和自定义验证
为什么选择 MongoDB?
MongoDB 提供了许多优势,其中一些包括:
-
灵活的文档:MongoDB 的集合包含多个文档。一个集合下的每个文档可以有不同的字段名,并且可以有不同的尺寸,这意味着我们不需要定义模式。
-
无复杂关系:MongoDB 中的文档以 JSON 文档的形式存储,这意味着我们不再需要费心学习应用程序各个组件之间的关系。
-
易于扩展:MongoDB 易于扩展,因为它通过使用称为分片的方法来最小化数据库大小。分片是一种数据库分区方法,允许我们将大型数据库分割成更小的部分。
MongoDB 查询
我们在 第一章,“MEVN 简介”中简要回顾了 Mongo 查询的外观。在这里,我们将深入探讨这些查询。
我们需要做的第一件事是启动 MongoDB 服务器。我们可以使用以下命令来完成:
$ mongod
现在,让我们通过在终端中键入 mongo 来打开 mongo shell。当我们进入 mongo shell 时,要显示数据库列表,我们键入 show dbs。
如果你看到数据库在列表中,键入 use {database_name} 以开始使用此数据库。如果我们还没有创建我们的数据库,只需使用 use {database_name} 就会为我们创建一个数据库。就这么简单。对于这个练习,让我们创建一个名为 mongo_test_queries 的数据库。为此,我们需要使用:
> use mongo_test_queries
这应该在终端中输出以下内容:
# switched to db mongo_test_queries
现在,一旦我们进入数据库,我们首先需要的是一个集合。我们有一个数据库,但没有集合。在 MongoDB 中创建集合的最佳方式是通过插入一个文档。这不仅初始化了一个集合,还将文档添加到该集合中。就这么简单。现在,让我们继续学习 Mongo 查询。
创建文档
在 MongoDB 中创建文档有不同的查询,例如 insertOne()、insertMany() 和 insert()。
insertOne()
insertOne() 命令将单个文档添加到我们的集合中。例如:
> db.users.insertOne(
{
name: "Brooke",
email: "brooke@app.com",
address: 'Kathmandu'
}
)
此命令仅接受一个参数,即一个对象,我们可以传递我们想要为 users 集合指定的字段名称和值。当我们运行上述代码时,我们应该在 Mongo shell 的终端中得到以下输出:
它返回刚刚创建的文档的 _id。我们已经成功在 users 集合中创建了一个集合和文档。
insertOne() 和 insertMany() 命令仅适用于 Mongo 版本 3.2 或更高版本。
insertMany()
此命令用于将多个文档插入到集合中。在先前的示例中,我们看到了 insertOne() 命令接受一个参数,该参数是一个对象。insertMany() 命令接受一个数组作为参数,这样我们就可以在它内部传递多个对象,并将多个文档插入到集合中。让我们看一个例子:
> db.users.insertMany(
[
{ name: "Jack", email: "jack@mongo.com" },
{ name: "John", email: "john@mongo.com" },
{ name: "Peter", email: "peter@mongo.com" }
]
)
此代码片段在 users 集合中创建了三个文档。当我们运行此命令时,输出应该是:
insert()
此命令将单个文档以及多个文档插入到集合中。它同时完成了 insertOne() 和 insertMany() 命令的工作。要插入单个文档,我们可以使用:
> db.users.insert(
{ name: "Mike", email: "mike@mongo.com" }
)
如果命令执行成功,我们应该看到以下输出:
现在,如果我们想插入多个文档,我们可以简单地使用:
> db.users.insert(
[
{ name: "Josh", email: "josh@mongo.com" },
{ name: "Ross", email: "ross@mongo.com" },
]
)
输出应该是以下内容:
检索文档
使用 find() 命令从 MongoDB 的集合中检索文档。有多种使用此命令的方法。
查找所有文档
要从集合中检索所有文档,我们可以使用:
> db.users.find()
我们也可以使用以下:
> db.users.find({})
这会输出以下内容:
通过过滤器查找文档
我们也可以在 find() 命令中添加过滤器。让我们检索名为 Mike 的文档。为此,我们可以使用:
> db.users.find({ name: 'Mike' })
它应该返回以下文档:
我们也可以使用 AND 或 OR 查询指定多个条件。
要查找名为 Mike 且电子邮件为 mike@mongo.com 的集合,我们可以简单地使用:
> db.users.find({ name: 'Mike', email: 'mike@mongo.com' })
逗号运算符表示 AND 运算符。我们可以使用逗号分隔的值指定任意数量的条件。前面的命令应该输出:
现在指定 AND 或逗号运算符的条件变得简单。如果我们想使用 OR 运算符,那么我们应该使用:
> db.users.find(
{
$or: [ { email: "josh@mongo.com" }, { name: "Mike" } ]
}
)
这里,我们说的是:检索那些名为 Mike 的用户文档,电子邮件可以是 josh@mongo.com。输出如下:
更新文档
就像 insert() 一样,在 MongoDB 中使用 update() 命令有三种方法:updateOne()、updateMany() 和 update()。
updateOne()
此命令只更新集合中的一个文档。在这里,我们插入了一些用户条目,电子邮件不正确。对于名为 Peter 的用户,电子邮件是 jack@mongo.com。让我们使用 updateOne() 更新此文档:
> db.users.updateOne(
{ "name": "Peter" },
{
$set: { "email": "peter@mongo.com" }
}
)
此命令将更新 Peter 的电子邮件为 peter@mongo.com。输出如下:
正如输出所示,modifiedCount 是 1,matchedCount 也是 1,这意味着找到了符合给定条件的文档并已更新。
updateMany()
此命令用于更新集合中的多个文档。updateOne() 和 updateMany() 更新文档的命令相同。要更新多个记录,我们指定条件并设置所需的值:
> db.users.updateOne(
{ "name": "Peter" },
{
$set: { "email": "peter@mongo.com" }
}
)
updateOne() 和 updateMany() 之间的唯一区别是 updateOne() 只更新第一个匹配的文档,而 updateMany() 更新所有匹配的文档。
update()
就像插入一样,update() 命令为 updateOne() 和 updateMany() 做了同样的事情。为了消除混淆,我们可以直接使用 update() 命令而不是 updateOne() 和 updateMany():
> db.users.update(
{ "name": "John" },
{
$set: { "email": "john@mongo.com" }
}
)
输出如下:
删除文档
MongoDB 提供了多个命令用于从集合中删除和移除文档。
deleteOne()
deleteOne() 只从集合中删除一个文档:
> db.users.deleteOne( { name: "John" } )
这会删除名为 John 的用户条目。输出如下:
如您在输出中看到的,deletedCount 是 1,这意味着记录已被删除。
deleteMany()
deleteMany() 命令与 deleteOne() 相同。唯一的区别是 deleteOne() 只删除与匹配过滤器匹配的单个条目,而 deleteMany() 则删除所有符合给定条件的文档:
> db.users.deleteMany( { name: "Jack" } )
输出如下:
remove()
remove() 命令用于从集合中删除单个条目以及多个条目。如果我们只想删除符合某些条件的单个文档,则可以传递我们希望删除的条目数。例如,让我们首先创建一个条目:
> db.users.insertOne({ name: 'Mike', email: 'mike@mike.com' })
使用此方法,现在我们有两个 Mike 的条目。现在,如果我们想使用 remove() 删除单个条目,我们可以这样做:
> db.users.remove({ name: 'Mike' }, 1)
输出如下:
如您所见,我们有两个名为 Mike 的条目,但它只删除了一个。同样,如果我们想删除所有文档,我们只需使用:
> db.users.remove({})
所有文档都将被删除。
我们讨论了如何在 Mongo 中查询文档的基本思路。要了解更多详细信息,请访问 docs.mongodb.com/v3.2/tutorial/query-documents/。
介绍 Mongoose
Mongoose 是一个优雅的 MongoDB 对象建模库,适用于 Node.js。正如我之前提到的,MongoDB 是一种无模式的数据库设计。虽然这有其自身的优势,但有时我们还需要添加某些验证,这意味着我们需要为我们的文档定义模式。Mongoose 提供了一种简单的方法来添加此类验证以及将文档中的字段类型化。
例如,要将数据插入 MongoDB 文档中,我们可以使用:
> db.posts.insert({ title : 'test title', description : 'test description'})
现在,如果我们想添加另一个文档并且想在其中添加一个额外的字段,我们可以使用:
> db.posts.insert({ title : 'test title', description : 'test description', category: 'News'})
这在 MongoDB 中是可能的,因为没有定义模式。这些类型的文档在构建应用程序时也是必需的。MongoDB 会默默地接受任何类型的文档。然而,有时我们需要文档看起来相似,以便在特定的验证中表现良好或具有特定的数据类型。在这种情况下,Mongoose 就派上用场了。我们也可以利用这些功能与原始 MongoDB 一起使用,但在 MongoDB 中编写验证是一个极其痛苦的任务。这就是为什么创建 Mongoose 的原因。
Mongoose 是用 Node.js 编写的 Mongo 数据建模技术。Mongoose 集合中的每个文档都需要固定数量的字段。我们必须显式定义一个 Schema 并遵守它。一个 Mongoose 模式的例子是:
const UserSchema = new Schema({
name: String,
bio: String,
extras: {}
})
这意味着名称和描述字段必须是字符串,而额外字段可以是一个完整的 JSON 对象,在其中我们可以存储嵌套值。
安装 Mongoose
就像任何其他包一样,Mongoose 可以使用 NPM 在我们的项目中安装。在上一章中创建的 express_app 文件夹内的终端中运行以下命令来安装 Mongoose:
$ npm install mongoose --save
如果安装成功,我们应该在我们的 package.json 文件中添加一行:
将 Mongoose 连接到 MongoDB
一旦安装了 Mongoose,我们必须将其连接到 MongoDB 才能开始使用它。使用 Mongoose 来做这件事非常直接;我们只需在 app.js 文件中添加一段代码来 require Mongoose,并使用 mongoose.connect 方法将其连接到数据库。让我们继续这样做。在 app.js 文件中,添加以下代码:
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
这将把 Mongoose 模块导入到我们的代码库中。
现在,为了连接到 MongoDB 数据库,请在我们的 app.js 文件中添加以下代码行:
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var app = express();
//connect to mongodb
mongoose.connect('mongodb://localhost:27017/express_app', function() {
console.log('Connection has been made');
})
.catch(err => {
console.error('App starting error:', err.stack);
process.exit(1);
});
// Require file system module
var fs = require('file-system');
这与我们的 Mongoose 数据库建立了一个连接。现在,让我们使用以下命令运行应用程序:
$ nodemon app.js
并在我们的终端中显示一条消息,如果成功或失败:
就这样!我们已经成功连接到我们的 MongoDB 数据库。这里的 URL 是本地托管的数据库名称。
在 Mongoose 中创建记录
让我们从在应用程序的 express_app 中创建一个新的模型开始。在项目的根目录下创建一个名为 models 的文件夹,并将其命名为 User.js。
我们使用文件名的首字母大写。同样,我们使用 models 的单数形式。与此相反,对于 controllers,我们使用复数形式和小写字母,例如 users.js。
一旦我们创建了文件,将以下代码粘贴到其中:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
name: String,
email: String
})
const User = mongoose.model("User", UserSchema)
module.exports = User
这里的第一行只是导入 Mongoose 模块。这个 Mongoose 包为我们提供了几个属性,其中之一是定义 Schema。现在,这里的原始 Schema 定义是以下高亮部分:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
name: String,
email: String
})
const User = mongoose.model("User", UserSchema)
module.exports = User
这所做的就是在我们的 User 数据模型中添加了一个验证,其中说明总共必须有两个字段。在为 Mongoose 集合创建文档时,它不会接受一个或多个数据字段。此外,它还向这个 Schema 添加了一个验证层,说明两个字段,即 name 和 email,应该是一个有效的字符串。它不会接受整数、布尔值或这两个字段之外的任何其他类型。这就是我们定义 Schema 的方式:
const mongoose = require("mongoose")
const Schema = mongoose.Schema
const UserSchema = new Schema({
name: String,
email: String
})
const User = mongoose.model("User", UserSchema)
module.exports = User
这段代码的高亮部分表示创建模型的方式。该方法的第一参数是我们的模型名称,它映射到集合名称的相应复数形式。因此,当我们创建一个 User 模型时,这会自动映射到我们数据库中的 user 集合。
现在,要创建一个用户,首先要创建一个资源:
const user_resource = new User({
name: 'John Doe',
email: 'john@doe.com'
})
现在,最后,真正创建 user 的部分是:
user_resource.save((error) => {
if(error)
console.log(error);
res.send({
success: true,
code: 200,
msg: "User added!"
})
})
之前的代码使用了一个名为save的 Mongoose 函数。save方法有一个用于错误处理的回调函数。当我们将资源保存到数据库时遇到错误,我们可以做任何我们想做的事情:
user_resource.save((error) => {
if(error)
console.log(error);
res.send({
success: true,
code: 200,
msg: "User added!"
})
})
res.send方法允许我们在资源成功保存到数据库时向客户端发送我们想要的内容。对象中的第一个元素是success: true,表示执行是否成功。第二个元素是状态码或响应码。一个200响应码表示成功执行。我们将在后续章节中进一步讨论这一点。最后一个元素是发送给客户端的消息;用户在前端会看到这个消息。
这就是我们在 Mongoose 中创建资源的方式。
从 Mongoose 获取记录
现在我们已经成功创建了一个用户,我们在数据库的users集合中有一个记录。在我们的客户端中,有两种方式可以获取这个记录:获取所有用户的记录或者获取一个特定的用户。
获取所有记录
Mongoose 模型有很多开箱即用的方法,可以使我们的工作更轻松。其中两种方法是find()和findById()。在 MongoDB 中,我们看到了如何通过原始 MongoDB 查询检索集合的记录数据。这与此类似,唯一的区别是 Mongoose 有一个非常简单的方式来做到这一点。我建议你先学习 MongoDB 而不是 Mongoose,因为 MongoDB 可以给你一个数据库的整体概念,你将学习数据库的基础知识和其查询。Mongoose 只是在 MongoDB 之上添加了一层,使其看起来更容易,以便更快地开发。
这样,让我们来看看这里的代码片段:
User.find({}, 'name email', function (error, users) {
if (error) { console.error(error); }
res.send({
users: users
})
})
Mongoose 模型User调用一个名为find()的方法。第一个参数是我们的查询字符串,它是空的:前一个查询中的{}。因此,如果我们想检索所有同名用户,比如 Peter,那么我们可以将那个空{}替换为{ name: 'Peter'}。
第二个参数表示我们想从数据库中检索哪些字段。如果我们想检索所有字段,可以将其留空,或者我们也可以在这里指定。对于这个例子,我们只是检索用户名和电子邮件。
第三个参数附加了一个回调函数。这个函数有两个参数,与create方法不同。第一个参数处理错误。如果执行未成功完成,它返回一个错误,我们可以按我们想要的方式自定义它。第二个参数是这里的一个重要参数;它返回执行成功时的响应。在这种情况下,users参数是从users集合中检索到的对象的数组。这个调用的输出将是:
users: [
{
name: 'John Doe',
email: 'john@doe.com'
}
]
现在我们已经从users集合中获取了所有记录。
获取特定记录
这与从集合中检索所有记录一样简单。我们在上一节中讨论了使用 find()。要检索单个记录,我们必须使用 findById() 或 findOne(),或者我们也可以使用 where 查询。where 查询与我们之前讨论的相同,当时我们需要传递一个参数来检索属于同一类别的记录。
让我们继续使用以下查询:
User.findById(1, 'name email', function (error, user) {
if (error) { console.error(error); }
res.send(user)
})
如您所见,find() 和 findById() 的语法相似。它们接受相同数量的参数,表现相同。这两个方法之间的唯一区别是,前面的 find() 方法返回了一个记录数组作为响应,而 findById() 返回一个单个对象。因此,前面查询的响应将是:
{
name: 'John Doe',
email 'john@doe.com'
}
就这么简单 - 简单!
在 Mongoose 中更新记录
让我们继续更新集合中的记录。更新集合记录的方法也很多,就像从集合中检索数据一样。在 Mongoose 中更新文档是 read(读取)和 create(保存)方法的组合。要更新文档,我们首先需要使用 Mongoose 的读取查询找到该文档,然后更改该文档,最后保存更改。
findById() 和 save()
让我们看看以下示例:
User.findById(1, 'name email', function (error, user) {
if (error) { console.error(error); }
user.name = 'Peter'
user.email = 'peter@gmail.com'
user.save(function (error) {
if (error) {
console.log(error)
}
res.send({
success: true
})
})
})
因此,我们首先需要找到用户文档,我们通过 findById() 来做这件事。此方法返回具有给定 ID 的用户。现在我们有了这个用户,我们可以更改我们想要的任何内容。在前面的例子中,我们更改了那个人的姓名和电子邮件。
现在重要的部分来了。更新此用户文档的工作是由这里的 save() 方法完成的。我们已经通过以下方式更改了用户的姓名和电子邮件:
user.name = 'Peter'
user.email = 'peter@gmail.com'
我们正在直接更改最初通过 findById() 返回的对象。现在,当我们使用 user.save() 时,此方法会使用新的姓名和电子邮件覆盖此用户之前的所有值。
我们还可以使用其他方法来更新 Mongoose 中的文档。
findOneAndUpdate()
当我们想要更新单个条目时,可以使用此方法。例如:
User.findOneAndUpdate({name: 'Peter'}, { $set: { name: "Sara" } }, function(err){
if(err){
console.log(err);
}
});
如您所见,第一个参数定义了描述我们想要更新的记录的准则,在这种情况下,是名为 Peter 的用户。第二个参数是我们定义要更新的 user 的哪些属性的对象,它由 { $set: { name: "Sara" } 定义。这会将 Peter 的 name 设置为 Sara。
现在,让我们对前面的代码进行一些小的修改:
User.findOneAndUpdate({name: 'Peter'}, { $set: { name: "Sara" } }, function(err, user){
if(err){
console.log(err);
}
res.send(user);
});
在这里,请注意,我向回调函数 user 添加了一个第二个参数。这样做的作用是,当 Mongoose 完成在数据库中更新该文档后,它会返回该对象。当我们更新记录后想要做出一些决定,并想要操作新更新的文档时,这非常有用。
findByIdAndUpdate()
这与findOneAndUpdate()有些相似。此方法接受一个 ID 作为参数,与findOneAndUpdate()不同,后者我们可以添加自己的条件,并更新该文档:
User.findByIdAndUpdate(1, { $set: { name: "Sara" } }, function(err){
if(err){
console.log(err);
}
});
这里的唯一区别在于,第一个参数接受一个单独的整数值,即文档的 ID,而不是一个对象。此方法还会返回正在更新的对象。因此,我们可以使用:
User.findByIdAndUpdate(1, { $set: { name: "Sara" } }, function(err){
if(err, user){
console.log(err);
}
res.send(user);
});
在 Mongoose 中删除记录
正如 Mongoose 中有许多创建、获取和更新记录的方法一样,它也提供了几种从集合中删除记录的方法,例如remove()、findOneAndRemove()和findByIdAndRemove()。我们可以使用remove()来删除一个或多个文档。我们也可以先找到我们想要删除的文档,然后使用remove()命令仅删除那些文档。如果我们想根据某些条件找到特定的文档,我们可以使用findOneAndRemove()。当我们知道要删除的文档的 ID 时,我们可以使用findByIdAndRemove()。
remove()
让我们看看使用此方法的示例:
User.remove({
_id: 1
}, function(err){
if (err)
res.send(err)
res.send({
success: true
})
})
remove()方法的第一个参数接受过滤条件,指定我们想要删除哪个用户。它接受一个 ID 作为参数。它找到具有给定 ID 的用户,并从集合中删除该文档。第二个参数是之前提到的回调函数。如果上述操作出现错误,它将返回一个错误,我们可以使用它来更好地处理应用程序中发生的异常或错误。在成功的情况下,我们可以定义自己的逻辑来决定返回什么。在前面的例子中,我们返回了{ success: true }。
findOneAndRemove
findOneAndRemove()的行为与remove()相同,并且接受相同数量的参数:
User.findOneAndRemove({
_id: 1
}, function(err){
if (err)
res.send(err)
res.send({
success: true
})
})
我们只需要定义我们想要删除的文档的筛选条件。
现在,我们也可以修改前面的代码:
User.findOneAndRemove({
_id: 1
}, function(err, user){
if (err)
res.send(err)
res.send({
success: true,
user: user
})
})
在这里,我已经突出显示了新增的代码片段。我们还可以向回调函数传递第二个参数,该参数返回被删除的user对象。现在,如果我们想在前端显示某些消息并添加一些用户属性,如user的name或email,这将非常有用。例如,如果我们想在前端显示一条消息,说明名为{x}的用户已被删除,那么我们可以在这里传递user或其他user属性;在这种情况下,是名称,将在前端显示。
remove()和findOneAndRemove()之间的主要区别在于,remove()不会返回被删除的文档,而findOneAndRemove()会。现在我们知道何时使用这两种方法。
findByIdAndRemove()
这与findOneAndRemove()相同,但总是需要传递一个id作为参数:
User.findByIdAndRemove(1, function(err){
if (err)
res.send(err)
res.send({
success: true
})
})
你在findOneAndRemove()和前面的findByIdAndRemove()代码之间发现了任何区别吗?如果我们查看这个方法的第一参数,它只接受一个简单的整数值,即文档 ID。现在,如果我们查看前面的findOneAndRemove()代码,我们会注意到我们在第一个参数中传递了一个对象。这是因为对于findOneAndRemove(),我们除了 ID 之外还可以传递其他参数。例如,我们也可以在findOneAndRemove()的参数中传递{ name: 'Anita' }。但是,对于findByIdAndRemove(),正如方法名所暗示的,我们不需要传递对象,只需要传递表示文档 ID 的整数。
它在参数中找到指定 ID 的文档,并将其从集合中删除。就像findOneAndRemove()一样,这个操作也会返回被删除的文档。
使用 Mongoose 添加验证
Mongoose 中的验证是在模式级别定义的。验证可以设置在字符串和数字中。Mongoose 为我们提供了字符串和数字的内置验证技术。此外,我们也可以根据我们的需求自定义这些验证。由于验证是在模式中定义的,因此当我们在任何文档上调用save()方法时,它们会被触发。如果我们只想测试这些验证,我们也可以通过仅通过{doc}.validate()执行验证方法来做到这一点。
validate()也是中间件,这意味着它在以异步方式执行某些方法时具有控制权。
默认验证
让我们谈谈 Mongoose 为我们提供的默认验证。这些也被称为内置验证器。
required()
required()验证器检查我们添加验证的字段是否有值。在之前的User模型中,我们有以下代码:
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var UserSchema = new Schema({
name: String,
email: String
});
var User = mongoose.model("User", UserSchema);
module.exports = User;
这段代码也与用户的字段相关联的验证有关。它要求用户的名字和电子邮件必须是字符串,而不是数字、布尔值或其他任何东西。但这段代码并没有确保用户的名字和电子邮件字段已被设置。
因此,如果我们想添加一个required()验证,代码应该这样修改:
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var UserSchema = new Schema({
name: {
required: true
},
email: {
required: true
}
});
var User = mongoose.model("User", UserSchema);
module.exports = User;
如您所见,我们已经将 name 键的值从字符串更改为对象。在这里,我们可以添加我们想要的任何验证。因此,添加的验证required: true检查在将文档保存到集合之前,用户的名字和电子邮件是否已设置某个值。如果验证未通过,它将返回一个错误。
我们还可以在验证返回错误时传递一个消息。例如:
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var UserSchema = new Schema({
name: {
required: [true, 'Let us know you by adding your name!']
},
email: {
required: [true, 'Please add your email as well.']
}
});
var User = mongoose.model("User", UserSchema);
module.exports = User;
这样,我们也可以根据我们的需求自定义消息。非常酷,对吧?
类型验证
类型验证方法定义了文档中字段的类型。类型的不同变体可以是String、boolean和number。
String
字符串本身下面有几个验证器,例如enum、match、maxlength和minlength。
maxlength 和 minlength 定义了字符串的长度。
数字
数字有两个验证器:min 和 max。min 和 max 的值定义了集合中字段值的范围。
自定义验证
如果默认的内置验证不够用,我们还可以添加自定义验证。我们可以传递一个 validate 函数并将我们的自定义代码写入该函数。让我们看一个例子:
var userSchema = new Schema({
phone: {
type: String,
validate: {
validator: function(v) {
return /\d{3}-\d{3}-\d{4}/.test(v);
},
message: '{VALUE} is not a valid phone number!'
}
}
});
这里,我们向 Schema 传递了一个 validate 方法。它接受一个验证器函数,我们可以在其中添加自己的验证代码。前面的方法检查用户的电话号码字段是否处于正确的格式。如果它未通过验证,则显示消息 {value} 不是一个有效的电话号码}。
我们还可以在 Mongoose 中添加嵌套验证:例如,如果我们的用户集合中的名称保存为 { name: { first_name: 'Anita', last_name: 'Sharma' } },我们需要为 first_name 和 last_name 添加验证。为此,我们可以使用:
var nameSchema = new Schema({
first_name: String,
last_name: String
});
userSchema = new Schema({
name: {
type: nameSchema,
required: true
}
});
首先,我们定义一个低级对象的 Schema,它包括 first_name 和 last_name 字段。然后,对于 userSchema,我们将 nameSchema 传递给名称字段。
记住,我们无法在这个 Schema 中添加嵌套验证,如下所示:
var nameSchema = new Schema({
first_name: String,
last_name: String
});
personSchema = new Schema({
name: {
type: {
first_name: String,
last_name: String
},
required: true
}
});
你可以在这里查看 Mongoose 验证的相关信息:mongoosejs.com/docs/validation.html。
摘要
在本章中,我们介绍了 MongoDB 的基本信息和它的好处,如何在 MongoDB 中进行 CRUD 操作和查询,以及使用 Mongoose 的基本验证。
在下一章中,我们将进一步讨论我们的应用程序中的 REST API 和 RESTful 架构设计。
第四章:介绍 REST API
应用程序编程接口(API)通常用于从一个应用程序获取数据到另一个应用程序。不同领域有不同的 API,例如硬件和编程,但我们将只讨论 Web API。Web API 是一种提供多个应用程序之间通信接口的 Web 服务。使用这些 API,一个应用程序的数据通过 HTTP 协议发送到另一个应用程序。
在本章中,我们将讨论:
-
REST 架构和 RESTful API
-
HTTP 动词和状态码
-
使用 Postman 开发和测试 API
Web API 的工作方式与浏览器如何与我们的应用程序服务器交互相似。客户端从服务器请求一些数据,服务器以格式化的数据对客户端做出响应;API 做的是类似的事情。例如,多个应用程序之间事先有一个合同。所以,如果有两个应用程序需要共享数据,那么一个应用程序将向另一个应用程序提交一个请求,说明它需要以这种格式的数据。当另一个应用程序收到请求时,它会从其服务器获取数据,并以结构化和格式化的数据对客户端或请求者做出响应。
Web API 被分为简单对象访问协议(SOAP)、远程过程调用(RPC)或表示性状态转移(REST)类别。这些 API 的响应格式可以是各种形式,如 XML、JSON、HTML、图片和视频。
API 也有不同的模型,例如公共 API 和私有 API:
-
私有 API:私有或内部 API 仅用于组织内部的内部应用程序
-
公共 API:公共或外部 API 设计的方式使得它们可以被组织外部的公众共享
什么是 REST?
REST 是一种通过 HTTP 协议在多个应用程序之间交换数据的 Web 服务。RESTful Web 服务是可扩展的且易于维护的。
这里有一个简单的图解,说明了 REST Web Service 是如何工作的:
如图中所示,客户端通过调用 Rest Web Service Server 来请求一些数据。在这里,当我们发送一个 HTTP 请求时,我们也会提供一些头部信息,例如我们希望作为响应的数据类型。这些响应可以是 JSON、XML、HTML 或其他任何形式。当服务器接收到请求并从存储中提取数据时,它不会简单地以数据库资源的形式返回响应。它发送这些资源的表示。这就是为什么它被称为表示性。当服务器以这种格式化的数据对客户端做出响应时,我们应用程序的状态发生了变化。这就是为什么它被称为状态转移。
介绍 REST API
REST API 是基于 RESTful 架构设计的。遵循 RESTful 架构原则构建的 API 被称为 RESTful API。RESTful 架构也被称为 无状态架构,因为客户端和服务器之间的连接不会被保留。在客户端和服务器之间每次事务之后,连接都会被重置。
由于存在多个网络服务,我们必须能够选择我们的需求和需求,以便为我们的应用程序构建完美的 API。SOAP 和 REST 协议都有一些优点和局限性。
SOAP 协议是在 1998 年由 Dave Winer 设计的。它使用 可扩展标记语言 (XML) 进行数据交换。是否使用 SOAP 或 REST 取决于我们在开发时选择的编程语言以及应用程序的需求。
REST API 允许我们在 JSON/XML 数据格式之间进行应用程序间的通信。JSON/XML 是一种易于格式化和易于人类阅读的数据表示。通过 RESTful API,我们可以从一个应用程序到另一个应用程序执行 创建、读取、更新 和 删除 (CRUD) 操作。
REST API 的好处
REST API 提供了许多好处。以下是我们通过使用 REST API 可以获得的一些优势:
-
从一个应用程序向另一个应用程序发送请求和获取响应非常容易。
-
响应可以以 JSON 或 XML 的人类可读格式检索。
-
所有的操作都是以 URI 的形式进行的,这意味着每个请求都通过 URI 来标识。
-
客户端和服务器之间的分离使得在需要时可以轻松迁移到不同的服务器,并且更改最小。客户端和服务器之间的隔离也使得扩展变得容易。
-
它与任何编程语言无关。无论我们使用 PHP、JAVA、Rails、Node.js 等何种编程语言,都可以实现 REST 架构。
-
开始使用非常容易,学习曲线也很短。
HTTP 动词
HTTP 动词是用来定义我们对资源想要执行的操作的不同方法。最常用的 HTTP 动词是 GET、POST、PUT、PATCH 和 DELETE。HTTP 动词是使多个应用程序之间通信成为可能请求方法。这些 HTTP 动词使得在不需要完全更改 URL 的情况下对资源执行多个操作成为可能。让我们更详细地看看这些。
GET
GET 请求是无效请求。这在我们想要获取资源信息时使用。这不会修改或删除资源。GET 请求的等效 CRUD 操作是 READ,这意味着它只获取信息,仅此而已。一个 GET 请求的示例 URL 是:
- 要获取所有记录:
GET http://www.example.com/users
- 要获取单个用户的信息:
GET http://www.example.com/users/{user_id}
POST
对于POST请求的等效 CRUD 操作是CREATE。这用于向集合中添加新记录。由于这会改变服务器状态,因此这不是一个幂等请求。如果我们两次以相同的参数请求POST方法,那么将在数据库中创建两个相同的新资源。以下是一个POST请求的示例 URL:
POST http://www.example.com/users/
PUT
PUT请求用于创建或更新记录。如果资源尚不存在,则创建新记录;如果资源已存在,则更新现有记录。等效的 CRUD 操作是update()。它替换了资源的现有表示。以下是一个PUT请求的示例 URL:
PUT http://www.example.com/users/
DELETE
这用于从集合中删除资源。等效的 CRUD 操作是delete()。
以下是一个DELETE请求的示例 URL:
DELETE http://www.example.com/users/{user_id}
HTTP 状态码
状态码是服务器对请求做出的响应的一部分。它表示请求的状态,无论它是否成功执行。状态码有三个数字。第一个数字表示该响应的类别或类别。HTTP 状态码的范围是100-500。在本节中,我们将介绍一些主要的状态码。
2XX 代码
200 范围内的状态码是 API 中任何请求的成功范围。在 200 范围内,有许多代码表示不同的成功形式。以下是可用的许多状态码中的一些解释:
-
200 OK:这是一个标准响应。它只是表示请求成功。此状态码还返回请求执行的资源。
-
201 Created:这表示资源的成功创建。
-
204 No Content:此状态码成功执行请求,但不返回任何内容。
4XX 代码
当客户端发生错误时,会出现 400 范围内的状态码:
-
400 Bad Request:当请求参数格式不正确或语法错误时,服务器会返回 400 状态码。
-
401 Unauthorized:当未经授权的方尝试发送 API 请求时,会返回此状态码。这基本上检查了认证部分。
-
403 Forbidden:这与 401 有些相似。这检查执行 API 请求的方的授权。这通常在执行 API 的不同用户有不同的权限设置时进行。
-
404 Not Found:当服务器在数据库中找不到我们尝试执行某些操作的资源时,会返回此响应。
5XX 代码
状态码 500 表示在给定资源执行的动作执行过程中存在问题:
-
500 Internal Server Error:当动作未成功执行时,会显示此状态码。与 200 状态码一样,这是服务器在出现问题时返回的通用代码。
-
503 服务不可用:当我们的服务器没有运行时,会显示这个状态码。
-
504 网关超时:这表示请求已发送到服务器,但在给定时间内没有收到任何响应。
介绍 Postman
Postman 是一个让我们更快地开发和测试我们的 API 的工具。这个工具提供了一个 GUI,使得调整我们的 API 变得容易,从而减少了我们 API 的开发时间。我们还可以通过创建所有已开发的 API 的集合来维护历史记录。
Postman 也有不同的替代方案,例如 Runscope 和 Paw。我们将在这本书中使用 Postman。
安装 Postman
使用 Postman 有不同的方式:
- 我们可以通过以下方式获取 Chrome 扩展程序:如果您访问
chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en,我们会看到以下内容:
点击“添加到 Chrome”按钮,扩展程序将被安装。
-
我们可以通过以下方式下载适合我们操作系统的桌面应用程序:
我们在这本书中使用了桌面应用程序。
使用 Postman 测试 API
首先,让我们快速回顾一下到目前为止我们已经做了什么。在我们正在构建的应用程序中,app.js 文件应该包含以下代码:
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var fs = require('file-system');
var mongoose = require('mongoose');
var app = express();
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/tutorial2', {
useMongoClient: true
});
var db = mongoose.connection;
db.on("error", console.error.bind(console, "connection error"));
db.once("open", function(callback){
console.log("Connection Succeeded");
});
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
// uncomment after placing our favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Include controllers
fs.readdirSync("controllers").forEach(function (file) {
if(file.substr(-3) == ".js") {
const route = require("./controllers/" + file)
route.controller(app)
}
})
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
app.listen(3000, function() {
console.log('listening on 3000')
})
由于此文件是在我们通过命令行构建应用程序时自动生成的,它使用 TypeScript 语法。如果我们想使用 ES 6 语法,我们可以将 var 替换为 const。
在我们的 models/User.js 文件中,我们有以下内容:
const mongoose = require("mongoose")
const Schema = mongoose.Schema
const UserSchema = new Schema({
name: String,
email: String
})
const User = mongoose.model("User", UserSchema)
module.exports = User
此外,在 controllers/users.js 文件中,我们有以下内容:
module.exports.controller = (app) => {
// get homepage
app.get('/users', (req, res) => {
res.render('index', { title: 'Users' });
})
}
在用户控制器中添加 GET 端点
让我们在 controllers/users.js 中添加一个路由,该路由将从数据库中获取所有用户的记录。
目前,根据我们 users 控制器中的代码,当我们访问 http://localhost:3000/users 时,它只返回一个标题,Users。让我们修改这段代码以包含一个 GET 请求来获取所有用户请求。
获取所有用户
首先,使用 $ nodemon app.js 启动服务器。现在,在 controllers/users.js 中:
var User = require("../models/User");
module.exports.controller = (app) => {
// get all users
app.get('/users', (req, res) => {
User.find({}, 'name email', function (error, users) {
if (error) { console.log(error); }
res.send(users);
})
})
}
现在我们已经将代码设置好了,让我们使用 Postman 应用程序测试这个端点。在 Postman 应用程序中,在 URL 中添加必要的详细信息。当我们点击发送按钮时,我们应该看到以下响应:
_id 是用户的 MongoDB ID,这是 Mongoose 查询默认发送的,我们正在获取用户的名字和电子邮件。如果我们只想获取名字,我们可以在 users 控制器中更改我们的查询以只获取名字。
Postman 允许我们编辑端点,并且请求的开发变得容易。如果我们想使用自己的本地浏览器进行测试,我们也可以这样做。
我使用了一个名为 JSONview 的 Chrome 插件来格式化 JSON 响应。您可以从这里获取插件:
chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc.
如我之前所述,如果我们访问http://localhost:3000/users,我们应该能够看到类似以下的内容:
我们可以使用 Postman 提供的save查询功能在将来运行这些查询。只需点击右上角的保存按钮,然后在我们继续前进的过程中创建新的查询。
获取单个用户
如 HTTP 动词部分所述,要从集合中获取单个记录,我们必须在参数中传递用户的id以获取用户详细信息。从先前的 Postman 响应示例中,让我们选择一个id并使用它来获取用户的记录。首先,让我们在我们的控制器中添加端点。在controllers/users.js中添加以下代码行:
var User = require("../models/User");
module.exports.controller = (app) => {
// get all users
app.get('/users', (req, res) => {
User.find({}, 'name email', function (error, users) {
if (error) { console.log(error); }
res.send({
users: users
})
})
})
//get a single user details
app.get('/users/:id', (req, res) => {
User.findById(req.params.id, 'name email', function (error, user) {
if (error) { console.log(error); }
res.send(user)
})
})
}
现在,在 Postman 中创建一个新的查询,以下参数。我们将创建一个带有 URL http://localhost:3000/users/:user_id 的GET请求,其中user_id是您在数据库中创建的任何用户的id。使用此设置,我们应该能够看到如下内容:
查询应返回 URL 中给定 ID 的用户详细信息。
在用户控制器中添加 POST 端点
让我们看看一个例子。让我们创建一个 API,它将使用 MongoDB 的insert()命令将用户资源保存到数据库中。在用户控制器中添加一个新的端点:
// add a new user
app.post('/users', (req, res) => {
const user = new User({
name: req.body.name,
email: req.body.email
})
user.save(function (error, user) {
if (error) { console.log(error); }
res.send(user)
})
})
在 Postman 中,将方法设置为POST,URL 设置为http://localhost:3000/users,将参数设置为原始 JSON,并输入以下内容:
{
"name": "Dave",
"email": "dave@mongo.com"
}
与GET请求不同,我们必须在body参数中传递我们想要添加的用户的名字和电子邮件。现在,如果我们运行一个GET all users查询,我们应该能够看到这个新用户。如果我们用相同的参数运行两次POST请求,那么它将创建两个不同的资源。
在用户控制器中添加 PUT 端点
让我们更新一个 ID 为5a3153d7ba3a827ecb241779的用户(将此 ID 更改为您的文档 ID),这是我们刚刚创建的。让我们重命名电子邮件:为此,首先让我们在我们的用户控制器中添加端点,换句话说,在controllers/user.js中:
// update a user
app.put('/users/:id', (req, res) => {
User.findById(req.params.id, 'name email', function (error, user) {
if (error) { console.error(error); }
user.name = req.body.name
user.email = req.body.email
user.save(function (error, user) {
if (error) { console.log(error); }
res.send(user)
})
})
})
我们在这里所做的是,我们添加了一个用于PUT请求的端点,它接受名称和电子邮件作为参数并将其保存到数据库中。相应的 Postman 看起来如下所示:
在这里,我们可以看到用户的名字已经被更新。而且,如果我们查看请求参数,我们还添加了一个age参数。但由于我们在定义用户模型时没有添加age,它丢弃了年龄值但更新了其余部分。
我们还可以使用 PATCH 方法来更新资源。PUT 和 PATCH 方法的区别是:PUT 方法更新整个资源,而 PATCH 用于对资源进行部分更新。
在用户控制器中添加 DELETE 端点
同样地,对于删除操作,让我们在 controllers/users.js 中添加一个端点:
// delete a user
app.delete('/users/:id', (req, res) => {
User.remove({
_id: req.params.id
}, function(error, user){
if (error) { console.error(error); }
res.send({ success: true })
})
})
以下代码获取用户的 ID,并从数据库中删除具有给定 ID 的用户。在 Postman 中,端点将如下所示:
摘要
在本章中,我们学习了什么是 RESTful API,不同的 HTTP 动词和状态码,以及如何使用 Postman 开发和测试 RESTful API。
在下一章中,我们将进入 Vue.js 的介绍,并使用 Vue.js 构建一个应用程序。
4万+



被折叠的 条评论
为什么被折叠?



