ExpressJS - 快速指南
ExpressJS - 概述
ExpressJS 是一个 Web 应用程序框架,为您提供了一个简单的 API 来构建网站、Web 应用程序和后端。使用 ExpressJS,您无需担心低级协议、流程等。
什么是 Express?
Express 提供了一个最小的界面来构建我们的应用程序。它为我们提供了构建应用程序所需的工具。它非常灵活,因为 npm 上有许多模块可用,可以直接插入 Express。
Express 由 TJ Holowaychuk 开发,由 Node.js 基金会和众多开源贡献者维护。
为什么选择 Express?
与 Rails 和 Django 等竞争对手不同,它们有自己构建应用程序的方式,而 Express 没有"最佳方式"。它非常灵活且可插入。
Pug
Pug(以前称为 Jade)是一种用于编写 HTML 模板的简洁语言。它 −
- 生成 HTML
- 支持动态代码
- 支持可重用性 (DRY)
它是 Express 中最流行的模板语言之一。
MongoDB 和 Mongoose
MongoDB 是一个开源文档数据库,旨在简化开发和扩展。此数据库还用于存储数据。
Mongoose 是 node.js 的客户端 API,可轻松从 Express 应用程序访问我们的数据库。
ExpressJS - 环境
在本章中,我们将学习如何开始开发和使用 Express 框架。首先,您应该安装 Node 和 npm(节点包管理器)。如果您还没有这些,请转到 Node 设置 以在本地系统上安装 node。通过在终端中运行以下命令来确认 node 和 npm 已安装。
node --version npm --version
您应该会得到类似于以下内容的输出。
v5.0.0 3.5.2
现在我们已经设置了 Node 和 npm,让我们了解一下 npm 是什么以及如何使用它。
Node 包管理器 (npm)
npm 是 Node 的包管理器。npm Registry 是 Node.js、前端 Web 应用程序、移动应用程序、机器人、路由器以及 JavaScript 社区无数其他需求的开源代码包的公共集合。npm 允许我们访问所有这些包并在本地安装它们。您可以在 npmJS 上浏览 npm 上可用的软件包列表。
如何使用 npm?
使用 npm 安装软件包有两种方法:全局和本地。
全局 − 此方法通常用于安装开发工具和基于 CLI 的软件包。要全局安装软件包,请使用以下代码。
npm install -g <package-name>
本地 − 此方法通常用于安装框架和库。本地安装的软件包只能在安装的目录中使用。要在本地安装包,请使用与上述相同的命令,但不使用 -g 标志。
npm install <package-name>
每当我们使用 npm 创建项目时,我们都需要提供一个 package.json 文件,其中包含有关我们项目的所有详细信息。npm 使我们可以轻松设置此文件。让我们设置我们的开发项目。
步骤 1 − 启动您的终端/cmd,创建一个名为 hello-world 的新文件夹并 cd(创建目录)进入其中 −
![npm init info](/expressjs/images/environment_mkdir.jpg)
步骤 2 −现在使用 npm 创建 package.json 文件,使用以下代码。
npm init
它会要求您提供以下信息。
![npm init info](/expressjs/images/environment_npm.jpg)
只需继续按 Enter,然后在"作者姓名"字段中输入您的姓名。
步骤 3 − 现在我们已经设置了 package.json 文件,我们将进一步安装 Express。要安装 Express 并将其添加到我们的 package.json 文件中,请使用以下命令 −
npm install --save express
要确认 Express 已正确安装,请运行以下代码。
ls node_modules #(dir node_modules for windows)
提示 − --save 标志可替换为 -S 标志。此标志确保 Express 作为依赖项添加到我们的 package.json 文件中。这有一个好处,下次我们需要安装项目的所有依赖项时,我们只需运行命令 npm install,它将在此文件中找到依赖项并为我们安装它们。
这就是我们使用 Express 框架开始开发所需的全部内容。为了使我们的开发过程更加简单,我们将从 npm 安装一个工具 nodemon。只要我们对任何文件进行更改,此工具就会重新启动我们的服务器,否则我们需要在每次修改文件后手动重新启动服务器。要安装 nodemon,请使用以下命令 −
npm install -g nodemon
您现在可以开始使用 Express 了。
ExpressJS - Hello World
我们已经设置了开发,现在是时候开始使用 Express 开发我们的第一个应用程序了。创建一个名为 index.js 的新文件并在其中输入以下内容。
var express = require('express'); var app = express(); app.get('/', function(req, res){ res.send("Hello world!"); }); app.listen(3000);
保存文件,转到终端并输入以下内容。
nodemon index.js
这将启动服务器。要测试此应用程序,请打开浏览器并转到 http://localhost:3000,将显示一条消息,如以下屏幕截图所示。
![Hello world](/expressjs/images/hello_world.jpg)
应用程序如何工作?
第一行在我们的文件中导入 Express,我们可以通过变量 Express 访问它。我们使用它来创建一个应用程序并将其分配给 var app。
app.get(route, callback)
此函数告诉在给定路由上调用 get 请求时要做什么。回调函数有 2 个参数,request(req) 和 response(res)。请求 object(req) 表示 HTTP 请求,具有请求查询字符串、参数、正文、HTTP 标头等属性。同样,响应对象表示 Express 应用在收到 HTTP 请求时发送的 HTTP 响应。
res.send()
此函数将一个对象作为输入,并将其发送给请求客户端。这里我们发送字符串 "Hello World!"。
app.listen(port, [host], [backlog], [callback]])
此函数绑定并侦听指定主机和端口上的连接。端口是此处唯一必需的参数。
S.No. | 参数和说明 |
---|---|
1 | port 服务器应接受传入请求的端口号。 |
2 | host 域名。您需要在将应用部署到云时设置它。 |
3 | backlog 排队等待连接的最大数量。默认值为 511。 |
4 | callback 当服务器开始监听请求时调用的异步函数。 |
ExpressJS - 路由
Web 框架在不同的路由上提供 HTML 页面、脚本、图像等资源。
以下函数用于在 Express 应用程序中定义路由 −
app.method(path, handler)
此 METHOD 可应用于任何一个 HTTP 动词 - get、set、put、delete。还有一种替代方法,其执行与请求类型无关。
Path 是请求将运行的路由。
Handler 是一个回调函数,当在相关路由上找到匹配的请求类型时执行。例如,
var express = require('express'); var app = express(); app.get('/hello', function(req, res){ res.send("Hello World!"); }); app.listen(3000);
如果我们运行应用程序并转到 localhost:3000/hello,服务器在路由 "/hello" 处收到 get 请求,我们的 Express 应用程序将执行附加到此路由的 callback 函数并发送 "Hello World!" 作为响应。
![Hello](/expressjs/images/routing_hello.jpg)
我们还可以在同一路由上使用多种不同的方法。例如,
var express = require('express'); var app = express(); app.get('/hello', function(req, res){ res.send("Hello World!"); }); app.post('/hello', function(req, res){ res.send("You just called the post method at '/hello'! "); }); app.listen(3000);
要测试此请求,请打开您的终端并使用 cURL 执行以下请求 −
curl -X POST "http://localhost:3000/hello"
![Curl request](/expressjs/images/routing_curl.jpg)
Express 提供了一种特殊方法 all,可使用相同函数处理特定路由上的所有类型的 http 方法。要使用此方法,请尝试以下操作。
app.all('/test', function(req, res){ res.send("HTTP 方法对此路由没有任何影响!"); });
此方法通常用于定义中间件,我们将在中间件章节中讨论。
路由器
像上面这样定义路由非常繁琐。为了将路由与我们的主要 index.js 文件分开,我们将使用 Express.Router。创建一个名为 things.js 的新文件并在其中输入以下内容。
var express = require('express'); var router = express.Router(); router.get('/', function(req, res){ res.send('GET route on things.'); }); router.post('/', function(req, res){ res.send('POST route on things.'); }); //导出此路由器以在我们的 index.js 中使用 module.exports = router;
现在要在我们的 index.js 中使用此路由器,请在 app.listen 函数调用之前输入以下内容。
var express = require('Express'); var app = express(); var things = require('./things.js'); //index.js 和 things.js 都应该在同一目录中 app.use('/things', things); app.listen(3000);
在路由 '/things' 上的 app.use 函数调用将 things 路由器与此路由连接起来。现在,我们的应用在 '/things' 收到的任何请求都将由我们的 things.js 路由器处理。 things.js 中的 '/' 路由实际上是 '/things' 的子路由。访问 localhost:3000/things/,您将看到以下输出。
![Router Things](/expressjs/images/router_things.jpg)
路由器在分离关注点和将代码的相关部分放在一起方面非常有用。它们有助于构建可维护的代码。您应该在单个文件中定义与实体相关的路由,并使用上述方法将其包含在 index.js 文件中。
ExpressJS - HTTP 方法
HTTP 方法在请求中提供,并指定客户端请求的操作。下表列出了最常用的 HTTP 方法 −
S.No. | 方法和说明 |
---|---|
1 | GET GET 方法请求指定资源的表示。使用 GET 的请求应仅检索数据,不应产生其他影响。 |
2 | POST POST 方法请求服务器接受请求中包含的数据作为 URI 标识的资源的新对象/实体。 |
3 | PUT PUT 方法请求服务器接受请求中包含的数据作为对 URI 标识的现有对象的修改。如果不存在,则 PUT 方法应该创建一个。 |
4 | DELETE DELETE 方法请求服务器删除指定的资源。 |
这些是最常见的 HTTP 方法。要了解有关这些方法的更多信息,请访问 http://www.tutorialspoint.com/http/http_methods.htm。
ExpressJS - URL 构建
我们现在可以定义路由,但这些路由是静态或固定的。要使用动态路由,我们应该提供不同类型的路由。使用动态路由允许我们传递参数并基于它们进行处理。
这是一个动态路由的示例 −
var express = require('express'); var app = express(); app.get('/:id', function(req, res){ res.send('The id you specified is ' + req.params.id); }); app.listen(3000);
要测试此操作,请转到 http://localhost:3000/123。将显示以下响应。
![URL Building 1](/expressjs/images/url_building_id.jpg)
您可以将 URL 中的"123"替换为其他任何内容,更改将反映在响应中。上述内容的一个更复杂的例子是 −
var express = require('express'); var app = express(); app.get('/things/:name/:id', function(req, res) { res.send('id: ' + req.params.id + ' and name: ' + req.params.name); }); app.listen(3000);
要测试上述代码,请转到 http://localhost:3000/things/tutorialspoint/12345。
![URL Building 2](/expressjs/images/url_building_complex.jpg)
您可以使用 req.params 对象访问您在 URL 中传递的所有参数。请注意,以上 2 个是不同的路径。它们永远不会重叠。此外,如果您想在获取 '/things' 时执行代码,则需要单独定义它。
模式匹配的路由
您还可以使用 regex 来限制 URL 参数匹配。假设您需要 id 是一个 5 位长的数字。您可以使用以下路由定义 −
var express = require('express'); var app = express(); app.get('/things/:id([0-9]{5})', function(req, res){ res.send('id: ' + req.params.id); }); app.listen(3000);
请注意,这将仅匹配具有 5 位长 id 的请求。您可以使用更复杂的正则表达式来匹配/验证您的路由。如果您的所有路由均不符合请求,您将收到 "无法获取 <your-request-route>" 消息作为响应。使用此简单路由,此消息将被 404 未找到页面替换 −
var express = require('express'); var app = express(); //此处的其他路由 app.get('*', function(req, res){ res.send('Sorry, this is an invalid URL.'); }); app.listen(3000);
重要 − 这应该放在所有路由之后,因为 Express 会匹配 index.js 文件从开始到结束的路由,包括您需要的外部路由器。
例如,如果我们定义与上述相同的路由,则在使用有效 URL 请求时,将显示以下输出。 −
![正确的正则表达式](/expressjs/images/url_matching_correct.jpg)
而对于不正确的 URL 请求,则会显示以下输出。
![无效的正则表达式 (404)](/expressjs/images/url_pattern_invalid.jpg)
ExpressJS - 中间件
中间件函数是可以访问 请求对象 (req)、响应对象 (res) 以及应用程序的请求-响应周期中的下一个中间件函数的函数。这些函数用于修改 req 和 res 对象,以执行解析请求主体、添加响应标头等任务。
以下是中间件函数运行的简单示例 −
var express = require('express'); var app = express(); //简单请求时间记录器 app.use(function(req, res, next){ console.log("A new request received at " + Date.now()); //这个函数调用非常重要。它告诉需要进行更多处理 //当前请求所必需的,位于下一个中间件中 function route handler. next(); }); app.listen(3000);
服务器上的每个请求都会调用上述中间件。因此,每次请求后,我们都会在控制台中收到以下消息 −
在 1467267512545 收到新请求
要将其限制到特定路由(及其所有子路由),请将该路由作为 app.use() 的第一个参数提供。例如,
var express = require('express'); var app = express(); //用于记录请求协议的中间件函数 app.use('/things', function(req, res, next){ console.log("A request for things received at " + Date.now()); next(); }); // 发送响应的路由处理程序 app.get('/things', function(req, res){ res.send('Things'); }); app.listen(3000);
现在,只要您请求"/things"的任何子路由,它就会记录时间。
中间件调用顺序
Express 中关于中间件最重要的事情之一是它们在您的文件中写入/包含的顺序;如果路由匹配,则还需要考虑它们的执行顺序。
例如,在下面的代码片段中,第一个函数首先执行,然后是路由处理程序,然后是结束函数。此示例总结了如何在路由处理程序之前和之后使用中间件;以及如何将路由处理程序本身用作中间件。
var express = require('express'); var app = express(); //发送响应之前的第一个中间件 app.use(function(req, res, next){ console.log("Start"); next(); }); //路由处理程序 app.get('/', function(req, res, next){ res.send("Middle"); next(); }); app.use('/', function(req, res){ console.log('End'); }); app.listen(3000);
运行此代码后,当我们访问 '/' 时,我们会收到 Middle 响应,控制台上显示 −
Start End
下图总结了我们学到的有关中间件的知识 −
![Middleware](/expressjs/images/middleware_desc.jpg)
现在我们已经介绍了如何创建自己的中间件,让我们讨论一些最常用的社区创建的中间件。
第三方中间件
Express 的第三方中间件列表可在此处获得。以下是一些最常用的中间件;我们还将学习如何使用/安装这些 −
body-parser
这用于解析附加了有效负载的请求正文。要安装 body parser,我们需要使用 npm install --save body-parser 安装它,并安装它,在 index.js 中包含以下行 −
var bodyParser = require('body-parser'); //解析 URL 编码数据 app.use(bodyParser.urlencoded({ extended: false })) //解析 json 数据 app.use(bodyParser.json())
要查看 body-parser 的所有可用选项,请访问其 github 页面。
cookie-parser
它解析 Cookie 标头并使用以 cookie 名称为键的对象填充 req.cookies。要安装 cookie 解析器,我们需要使用 npm install --save cookie-parser 进行安装,并在 index.js 中包含以下几行 −
var cookieParser = require('cookie-parser'); app.use(cookieParser())
express-session
它使用给定的选项创建一个会话中间件。我们将在会话部分讨论它的用法。
ExpressJS 中有许多其他第三方中间件。但是,我们在这里只讨论了几个重要的中间件。
ExpressJS - 模板
Pug 是 Express 的模板引擎。模板引擎用于消除我们的服务器代码与 HTML 的混乱,将字符串疯狂地连接到现有的 HTML 模板。 Pug 是一个非常强大的模板引擎,它具有多种功能,包括过滤器、包含、继承、插值等。这方面有很多内容需要介绍。
要将 Pug 与 Express 一起使用,我们需要安装它,
npm install --save pug
现在 Pug 已安装,请将其设置为应用程序的模板引擎。您不需要"require"它。将以下代码添加到您的 index.js 文件中。
app.set('view engine', 'pug'); app.set('views','./views');
现在创建一个名为 views 的新目录。在其中创建一个名为 first_view.pug 的文件,并在其中输入以下数据。
doctype html html head title = "Hello Pug" body p.greetings#people Hello World!
要运行此页面,请将以下路由添加到您的应用 −
app.get('/first_template', function(req, res){ res.render('first_view'); });
您将获得输出为 − Hello World! Pug 将这个非常简单的标记转换为 html。我们不需要跟踪关闭标签,不需要使用 class 和 id 关键字,而是使用 '.' 和 '#' 来定义它们。上面的代码首先转换为 −
<!DOCTYPE html> <html> <head> <title>Hello Pug</title> </head> <body> <p class = "greetings" id = "people">Hello World!</p> </body> </html>
Pug 的功能远不止简化 HTML 标记。
Pug 的重要功能
现在让我们探索一下 Pug 的一些重要功能。
简单标签
标签根据其缩进进行嵌套。如上例所示,<title> 在 <head> 标签内缩进,因此它位于其中。但是 <body> 标签位于相同的缩进中,因此它是 <head> 标签的兄弟。
我们不需要关闭标签,只要 Pug 在相同或外部缩进级别遇到下一个标签,它就会为我们关闭标签。
要将文本放在标签内,我们有 3 种方法 −
空格分隔
h1 Welcome to Pug
管道文本
div | To insert multiline text, | You can use the pipe operator.
文本块
div. But that gets tedious if you have a lot of text. You can use "." at the end of tag to denote block of text. To put tags inside this block, simply enter tag in a new line and indent it accordingly.
注释
Pug 使用与 JavaScript(//) 相同的语法来创建注释。这些注释将转换为 html 注释 (<!--comment-->)。例如,
//This is a Pug comment
此注释将转换为以下内容。
<!--This is a Pug comment-->
属性
要定义属性,我们使用括号中以逗号分隔的属性列表。类和 ID 属性具有特殊表示。以下代码行涵盖了为给定的 html 标记定义属性、类和 id。
div.container.column.main#division(width = "100", height = "100")
此行代码转换为以下内容。 −
<div class = "container column main" id = "division" width = "100" height = "100"></div>
将值传递给模板
当我们呈现 Pug 模板时,我们实际上可以从我们的路由处理程序中向其传递一个值,然后我们可以在模板中使用该值。使用以下内容创建一个新的路由处理程序。
var express = require('express'); var app = express(); app.get('/dynamic_view', function(req, res){ res.render('dynamic', { name: "TutorialsPoint", url:"http://www.tutorialspoint.com" }); }); app.listen(3000);
并在 views 目录中创建一个名为 dynamic.pug 的新视图文件,代码如下 −
html head title=name body h1=name a(href = url) URL
在浏览器中打开 localhost:3000/dynamic_view;您应该得到以下输出 −
![模板中的变量](/expressjs/images/templating_variables.jpg)
我们还可以在文本中使用这些传递的变量。要在标签文本之间插入传递的变量,我们使用 #{variableName} 语法。例如,在上面的例子中,如果我们想放入 TutorialsPoint 的问候语,那么我们可以执行以下操作。
html head title = name body h1 Greetings from #{name} a(href = url) URL
这种使用值的方法称为插值。上面的代码将显示以下输出。−
![Templating Inter](/expressjs/images/templating_inter.jpg)
条件
我们也可以使用条件语句和循环结构。
考虑以下−
如果用户已登录,则页面应显示"嗨,用户",如果没有,则显示"登录/注册"链接。为了实现这一点,我们可以定义一个简单的模板,例如 −
html head title Simple template body if(user) h1 Hi, #{user.name} else a(href = "/sign_up") Sign Up
当我们使用我们的路由渲染它时,我们可以传递一个对象,如以下程序所示 −
res.render('/dynamic',{ user: {name: "Ayush", age: "20"} });
您将收到一条消息 − 嗨,Ayush。但如果我们不传递任何对象或传递没有用户密钥的对象,那么我们将获得一个注册链接。
包含和组件
Pug 提供了一种非常直观的方式来为网页创建组件。例如,如果您看到一个新闻网站,带有徽标和类别的标题始终是固定的。我们可以使用 include 功能,而不是将其复制到我们创建的每个视图中。以下示例展示了我们如何使用此功能 −
使用以下代码创建 3 个视图 −
HEADER.PUG
div.header. I'm the header for this website.
CONTENT.PUG
html head title Simple template body include ./header.pug h3 I'm the main content include ./footer.pug
FOOTER.PUG
div.footer. I'm the footer for this website.
按如下方式创建此路线 −
var express = require('express'); var app = express(); app.get('/components', function(req, res){ res.render('content'); }); app.listen(3000);
转到 localhost:3000/components,您将收到以下输出 −
![templating components](/expressjs/images/templating_components.jpg)
include 也可用于包含纯文本、css 和 JavaScript。
Pug 还有许多其他功能。但这些超出了本教程的范围。您可以在 Pug 进一步探索 Pug。
ExpressJS - 提供静态文件
静态文件是客户端从服务器直接下载的文件。创建一个新目录 public。默认情况下,Express 不允许您提供静态文件。您需要使用以下内置中间件启用它。
app.use(express.static('public'));
注意 − Express 查找相对于静态目录的文件,因此静态目录的名称不是 URL 的一部分。
请注意,根路由现在设置为您的公共目录,因此您加载的所有静态文件都将被视为公共文件。要测试这是否正常工作,请在新的 public 目录中添加任何图像文件,并将其名称更改为"testimage.jpg"。在您的视图中,创建一个新视图并包含此文件,例如 −
html head body h3 Testing static file serving: img(src = "/testimage.jpg", alt = "Testing Image
您应该得到以下输出 −
![静态文件示例](/expressjs/images/serve_static.jpg)
多个静态目录
我们还可以使用以下程序设置多个静态资产目录 −
var express = require('express'); var app = express(); app.use(express.static('public')); app.use(express.static('images')); app.listen(3000);
虚拟路径前缀
我们还可以提供用于提供静态文件的路径前缀。例如,如果您想提供像 '/static' 这样的路径前缀,则需要在 index.js 文件中包括以下代码 −
var express = require('express'); var app = express(); app.use('/static', express.static('public')); app.listen(3000);
现在,每当您需要包含一个文件时,例如,位于公共目录中的名为 main.js 的脚本文件,请使用以下脚本标记 −
<script src = "/static/main.js" />
当将多个目录作为静态文件提供时,此技术非常有用。这些前缀可以帮助区分多个目录。
ExpressJS - 表单数据
表单是 Web 不可或缺的一部分。我们访问的几乎每个网站都提供表单,用于为我们提交或获取一些信息。要开始使用表单,我们首先要安装 body-parser(用于解析 JSON 和 url 编码数据)和 multer(用于解析 multipart/form 数据)中间件。
要安装 body-parser 和 multer,请转到您的终端并使用 −
npm install --save body-parser multer
用以下代码替换您的 index.js 文件内容 −
var express = require('express'); var bodyParser = require('body-parser'); var multer = require('multer'); var upload = multer(); var app = express(); app.get('/', function(req, res){ res.render('form'); }); app.set('view engine', 'pug'); app.set('views', './views'); // 用于解析 application/json app.use(bodyParser.json()); // 用于解析 application/xwww- app.use(bodyParser.urlencoded({ extended: true })); //form-urlencoded // 用于解析 multipart/form-data app.use(upload.array()); app.use(express.static('public')); app.post('/', function(req, res){ console.log(req.body); res.send("recieved your request!"); }); app.listen(3000);
导入 body 解析器和 multer 后,我们将使用 body-parser 解析 json 和 x-www-form-urlencoded 标头请求,同时使用 multer 解析 multipart/form-data。
让我们创建一个 html 表单来测试一下。使用以下代码创建一个名为 form.pug 的新视图 −
html html head title Form Tester body form(action = "/", method = "POST") div label(for = "say") Say: input(name = "say" value = "Hi") br div label(for = "to") To: input(name = "to" value = "Express forms") br button(type = "submit") Send my greetings
使用以下命令运行您的服务器。
nodemon index.js
现在转到 localhost:3000/ 并根据需要填写表单,然后提交。将显示以下响应 −
![对表单提交的响应](/expressjs/images/form_received.jpg)
查看您的控制台;它将以 JavaScript 对象的形式显示您的请求正文,如以下屏幕截图所示 −
![表单的控制台输出](/expressjs/images/form_console.jpg)
req.body 对象包含您解析的请求正文。要使用该对象的字段,只需像使用普通 JS 对象一样使用它们即可。
这是发送请求的最推荐方式。还有许多其他方法,但这些方法与本文无关,因为我们的 Express 应用将以相同的方式处理所有这些请求。要了解有关发出请求的不同方法的更多信息,请查看此页面。
ExpressJS - 数据库
我们不断收到请求,但最终没有将它们存储在任何地方。我们需要一个数据库来存储数据。为此,我们将使用名为 MongoDB 的 NoSQL 数据库。
要安装和了解 Mongo,请点击此链接。
为了将 Mongo 与 Express 结合使用,我们需要一个用于节点的客户端 API。我们有多种选择,但在本教程中,我们将坚持使用 mongoose。Mongoose 用于 MongoDB 节点中的文档建模。对于文档建模,我们创建一个模型(非常类似于面向文档编程中的类),然后我们使用此模型生成文档(就像我们在 OOP 中创建类的文档一样)。我们所有的处理都将在这些"文档"上完成,最后,我们将这些文档写入我们的数据库。
设置 Mongoose
现在您已经安装了 Mongo,让我们安装 Mongoose,就像我们安装其他节点包一样 −
npm install --save mongoose
在开始使用 mongoose 之前,我们必须使用 Mongo shell 创建一个数据库。要创建新数据库,请打开您的终端并输入"mongo"。Mongo shell 将启动,输入以下代码 −
use my_db
将为您创建一个新数据库。每当您打开 mongo shell 时,它将默认为"test"db,您必须使用与上面相同的命令更改为您的数据库。
要使用 Mongoose,我们将在 index.js 文件中需要它,然后连接到在 mongodb://localhost 上运行的 mongodb 服务。
var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/my_db');
现在我们的应用程序已连接到我们的数据库,让我们创建一个新的模型。此模型将充当我们数据库中的集合。要创建新模型,请在定义任何路由之前使用以下代码 −
var personSchema = mongoose.Schema({ name: String, age: Number, nationality: String }); var Person = mongoose.model("Person", personSchema);
上述代码定义了人员的架构,用于创建 Mongoose 模式 Person。
保存文档
现在,我们将创建一个新的 html 表单;此表单将帮助您获取人员的详细信息并将其保存到我们的数据库中。要创建表单,请在 views 目录中创建一个名为 person.pug 的新视图文件,其中包含以下内容 −
html head title Person body form(action = "/person", method = "POST") div label(for = "name") Name: input(name = "name") br div label(for = "age") Age: input(name = "age") br div label(for = "nationality") Nationality: input(name = "nationality") br button(type = "submit") Create new person
同时在 index.js 中添加一个 new get route 来呈现此文档 −
app.get('/person', function(req, res){ res.render('person'); });
转到"localhost:3000/person"以检查表单是否显示正确的输出。请注意,这只是 UI,它尚未工作。以下屏幕截图显示了表单的显示方式 −
![Mongoose Create](/expressjs/images/mongoose_create.jpg)
我们现在将在 '/person' 处定义一个 post 路由处理程序来处理此请求
app.post('/person', function(req, res){ var personInfo = req.body; //Get the parsed information if(!personInfo.name || !personInfo.age || !personInfo.nationality){ res.render('show_message', { message: "Sorry, you provided worng info", type: "error"}); } else { var newPerson = new Person({ name: personInfo.name, age: personInfo.age, nationality: personInfo.nationality }); newPerson.save(function(err, Person){ if(err) res.render('show_message', {message: "Database error", type: "error"}); else res.render('show_message', { message: "New person added", type: "success", person: personInfo}); }); } });
在上面的代码中,如果我们收到任何空字段或没有收到任何字段,我们将发送错误响应。但是,如果我们收到格式正确的文档,则我们从 Person 模型创建一个 newPerson 文档,并使用 newPerson.save() 函数将其保存到我们的数据库中。这是在 Mongoose 中定义的,并接受回调作为参数。此回调有 2 个参数 - 错误和响应。这些参数将呈现 show_message 视图。
要显示来自此路由的响应,我们还需要创建一个 show_message 视图。使用以下代码创建一个新视图 −
html head title Person body if(type == "error") h3(style = "color:red") #{message} else h3 New person, name: #{person.name}, age: #{person.age} and nationality: #{person.nationality} added!
成功提交 form(show_message.pug) 后,我们将收到以下响应 −
![Mongoose Response](/expressjs/images/mongoose_response.jpg)
我们现在有一个创建 persons 的接口。
检索文档
Mongoose 提供了很多用于检索文档的函数,我们将重点介绍其中的 3 个。所有这些函数也将回调作为最后一个参数,并且与 save 函数一样,它们的参数是 error 和 response。这三个函数如下 −
Model.find(conditions, callback)
此函数查找与 conditions 对象中的字段匹配的所有文档。Mongo 中使用的相同运算符也适用于 mongoose。例如,
Person.find(function(err, response){ console.log(response); });
这将从该人的收藏中获取所有文档。
Person.find({name: "Ayush", age: 20}, function(err, response){ console.log(response); });
这将获取字段名称为"Ayush"且年龄为 20 的所有文档。
我们还可以提供所需的投影,即所需的字段。例如,如果我们只想要国籍为"印度"的人的姓名,我们使用 −
Person.find({nationality: "Indian"}, "name", function(err, response){ console.log(response); });
Model.findOne(conditions, callback)
此函数始终获取单个最相关的文档。它具有与 Model.find() 完全相同的参数。
Model.findById(id, callback)
此函数将 _id(由 mongo 定义)作为第一个参数,一个可选的投影字符串和一个用于处理响应的回调。例如,
Person.findById("507f1f77bcf86cd799439011", function(err, response){ console.log(response); });
现在让我们创建一个查看所有人员记录的路线 −
var express = require('express'); var app = express(); var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/my_db'); var personSchema = mongoose.Schema({ name: String, age: Number, nationality: String }); var Person = mongoose.model("Person", personSchema); app.get('/people', function(req, res){ Person.find(function(err, response){ res.json(response); }); }); app.listen(3000);
更新文档
Mongoose 提供 3 个函数来更新文档。这些函数如下所述 −
Model.update(condition, updates, callback)
此函数接受条件并更新对象作为输入,并将更改应用于集合中符合条件的所有文档。例如,以下代码将更新所有 Person 文档中的国籍"American" −
Person.update({age: 25}, {nationality: "American"}, function(err, response){ console.log(response); });
Model.findOneAndUpdate(condition, updates, callback)
它根据查询找到一个文档,并根据第二个参数更新该文档。它还将回调作为最后一个参数。让我们执行以下示例来了解该函数
Person.findOneAndUpdate({name: "Ayush"}, {age: 40}, function(err, response) { console.log(response); });
Model.findByIdAndUpdate(id, updates, callback)
此函数更新由其 id 标识的单个文档。例如,
Person.findByIdAndUpdate("507f1f77bcf86cd799439011", {name: "James"}, function(err, response){ console.log(response); });
现在让我们创建一个更新人员的路由。这将是一个 PUT 路由,以 id 作为参数,并在有效负载中提供详细信息。
var express = require('express'); var app = express(); var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/my_db'); var personSchema = mongoose.Schema({ name: String, age: Number, nationality: String }); var Person = mongoose.model("Person", personSchema); app.put('/people/:id', function(req, res){ Person.findByIdAndUpdate(req.params.id, req.body, function(err, response){ if(err) res.json({message: "Error in updating person with id " + req.params.id}); res.json(response); }); }); app.listen(3000);
要测试此路由,请在您的终端中输入以下内容(将 id 替换为您创建的 people 中的 id)−
curl -X PUT --data "name = James&age = 20&nationality = American "http://localhost:3000/people/507f1f77bcf86cd799439011
这将使用上述详细信息更新与路由中提供的 id 关联的文档。
删除文档
我们已经介绍了创建、读取和更新,现在我们将了解如何使用 Mongoose 来删除文档。这里有 3 个函数,与更新完全相同。
Model.remove(condition, [callback])
此函数将条件对象作为输入,并删除所有符合条件的文档。例如,如果我们需要删除所有 20 岁的人,请使用以下语法 −
Person.remove({age:20});
Model.findOneAndRemove(condition, [callback])
此函数根据条件对象删除单个最相关的文档。让我们执行以下代码来理解这一点。
Person.findOneAndRemove({name: "Ayush"});
Model.findByIdAndRemove(id, [callback])
此函数删除由其 id 标识的单个文档。例如,
Person.findByIdAndRemove("507f1f77bcf86cd799439011");
现在让我们创建一个从数据库中删除人员的路由。
var express = require('express'); var app = express(); var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/my_db'); var personSchema = mongoose.Schema({ name: String, age: Number, nationality: String }); var Person = mongoose.model("Person", personSchema); app.delete('/people/:id', function(req, res){ Person.findByIdAndRemove(req.params.id, function(err, response){ if(err) res.json({message: "Error in deleting record id " + req.params.id}); else res.json({message: "Person with id " + req.params.id + " removed."}); }); }); app.listen(3000);
要检查输出,请使用以下 curl 命令 −
curl -X DELETE http://localhost:3000/people/507f1f77bcf86cd799439011
这将删除具有给定 id 的人员,并生成以下消息 −
{message: "Person with id 507f1f77bcf86cd799439011 removed."}
这总结了如何使用 MongoDB、Mongoose 和 Express 创建简单的 CRUD 应用程序。要进一步探索 Mongoose,请阅读 API 文档。
ExpressJS - Cookies
Cookies 是简单的小型文件/数据,通过服务器请求发送到客户端并存储在客户端。每次用户重新加载网站时,此 cookie 都会随请求一起发送。这有助于我们跟踪用户的操作。
以下是 HTTP Cookies 的众多用途 −
- 会话管理
- 个性化(推荐系统)
- 用户跟踪
要将 cookie 与 Express 一起使用,我们需要 cookie-parser 中间件。要安装它,请使用以下代码 −
npm install --save cookie-parser
现在要在 Express 中使用 cookie,我们将需要 cookie-parser。cookie-parser 是一个中间件,它 解析附加到客户端请求对象的 cookie。要使用它,我们将在我们的 index.js 文件中使用它;这可以像我们使用其他中间件一样使用。在这里,我们将使用以下代码。
var cookieParser = require('cookie-parser'); app.use(cookieParser());
cookie-parser 解析 Cookie 标头并使用由 cookie 名称键入的对象填充 req.cookies。要设置新 cookie,让我们在您的 Express 应用中定义一个新路由,例如 −
var express = require('express'); var app = express(); app.get('/', function(req, res){ res.cookie('name', 'express').send('cookie set'); //Sets name = express }); app.listen(3000);
要检查您的 cookie 是否已设置,只需转到浏览器,启动控制台,然后输入 −
console.log(document.cookie);
您将获得类似输出(您可能由于浏览器中的扩展而设置了更多 cookie)−
"name = express"
浏览器每次查询服务器时也会发回 cookie。要查看来自服务器的 cookie,请在路由中的服务器控制台上,将以下代码添加到该路由。
console.log('Cookies: ', req.cookies);
下次向此路由发送请求时,您将收到以下输出。
Cookies:{ name: 'express' }
添加带有过期时间的 Cookie
您可以添加过期的 Cookie。要添加过期的 Cookie,只需传递一个对象,并将属性"expire"设置为您希望其过期的时间。例如,
//从设置时间起 360000 毫秒后过期。 res.cookie(name, 'value', {expire: 360000 + Date.now()});
设置过期时间的另一种方法是使用 'maxAge' 属性。使用此属性,我们可以提供相对时间而不是绝对时间。以下是该方法的一个示例。
//此 cookie 也会在设置后 360000 毫秒过期。 res.cookie(name, 'value', {maxAge: 360000});
删除现有 Cookie
要删除 cookie,请使用 clearCookie 函数。例如,如果您需要清除名为 foo 的 cookie,请使用以下代码。
var express = require('express'); var app = express(); app.get('/clear_cookie_foo', function(req, res){ res.clearCookie('foo'); res.send('cookie foo cleared'); }); app.listen(3000);
在下一章中,我们将了解如何使用 cookies 来管理会话。
ExpressJS - 会话
HTTP 是无状态的;为了将一个请求与任何其他请求关联起来,您需要一种在 HTTP 请求之间存储用户数据的方法。Cookie 和 URL 参数都是在客户端和服务器之间传输数据的合适方式。但它们都是可读的,并且位于客户端。会话恰好解决了这个问题。您为客户端分配一个 ID,然后它使用该 ID 发出所有进一步的请求。与客户端相关的信息存储在与此 ID 关联的服务器上。
我们需要 Express-session,因此使用以下代码安装它。
npm install --save express-session
我们将把 session 和 cookie-parser 中间件放到位。在此示例中,我们将使用默认存储来存储会话,即 MemoryStore。切勿在生产环境中使用它。会话中间件会为我们处理所有事情,即创建会话、设置会话 cookie 并在 req 对象中创建会话对象。
每当我们再次从同一客户端发出请求时,我们都会存储他们的会话信息(假设服务器未重新启动)。我们可以向会话对象添加更多属性。在下面的示例中,我们将为客户端创建一个查看计数器。
var express = require('express'); var cookieParser = require('cookie-parser'); var session = require('express-session'); var app = express(); app.use(cookieParser()); app.use(session({secret: "Shh, its a secret!"})); app.get('/', function(req, res){ if(req.session.page_views){ req.session.page_views++; res.send("You visited this page " + req.session.page_views + " times"); } else { req.session.page_views = 1; res.send("Welcome to this page for the first time!"); } }); app.listen(3000);
上述代码的作用是,当用户访问网站时,它会为用户创建一个新会话并为其分配一个 cookie。下次用户访问时,将检查 cookie,并相应地更新 page_view 会话变量。
现在,如果您运行应用程序并转到 localhost:3000,将显示以下输出。
![First visit](/expressjs/images/session_first.jpg)
如果您重新访问该页面,页面计数器将增加。以下屏幕截图中的页面刷新了 42 次。
![首次访问](/expressjs/images/session_42.jpg)
ExpressJS - 身份验证
身份验证是一个过程,在此过程中,提供的凭据会与本地操作系统或身份验证服务器中授权用户信息数据库中的凭据进行比较。如果凭据匹配,则该过程完成,并授予用户访问权限。
为了创建身份验证系统,我们需要创建一个注册页面和一个用户密码存储。以下代码为我们创建一个帐户并将其存储在内存中。这只是为了演示;建议始终使用持久存储(数据库或文件)来存储用户信息。
var express = require('express'); var app = express(); var bodyParser = require('body-parser'); var multer = require('multer'); var upload = multer(); var session = require('express-session'); var cookieParser = require('cookie-parser'); app.set('view engine', 'pug'); app.set('views','./views'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(upload.array()); app.use(cookieParser()); app.use(session({secret: "Your secret key"})); var Users = []; app.get('/signup', function(req, res){ res.render('signup'); }); app.post('/signup', function(req, res){ if(!req.body.id || !req.body.password){ res.status("400"); res.send("Invalid details!"); } else { Users.filter(function(user){ if(user.id === req.body.id){ res.render('signup', { message: "User Already Exists! Login or choose another user id"}); } }); var newUser = {id: req.body.id, password: req.body.password}; Users.push(newUser); req.session.user = newUser; res.redirect('/protected_page'); } }); app.listen(3000);
现在对于注册表单,创建一个名为signup.jade的新视图。
SIGNUP.JADE
html head title Signup body if(message) h4 #{message} form(action = "/signup" method = "POST") input(name = "id" type = "text" required placeholder = "User ID") input(name = "password" type = "password" required placeholder = "Password") button(type = "Submit") Sign me up!
通过访问 localhost:3000/signup 检查此页面是否加载。
![Signup form](/expressjs/images/auth_signup.jpg)
我们已为两个字段设置了必需属性,因此支持 HTML5 的浏览器将不允许我们提交此表单,除非我们同时提供 ID 和密码。如果有人尝试使用 curl 请求注册而没有用户 ID 或密码,则会显示错误。在视图中创建一个名为 protected_page.pug 的新文件,内容如下 −
html head title Protected page body div Hey #{id}, How are you doing today? div Want to log out? div Logout
只有用户刚刚注册或登录后,该页面才可见。现在让我们定义它的路由以及登录和注销的路由 −
var express = require('express'); var app = express(); var bodyParser = require('body-parser'); var multer = require('multer'); var upload = multer(); var session = require('express-session'); var cookieParser = require('cookie-parser'); app.set('view engine', 'pug'); app.set('views','./views'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(upload.array()); app.use(cookieParser()); app.use(session({secret: "Your secret key"})); var Users = []; app.get('/signup', function(req, res){ res.render('signup'); }); app.post('/signup', function(req, res){ if(!req.body.id || !req.body.password){ res.status("400"); res.send("Invalid details!"); } else { Users.filter(function(user){ if(user.id === req.body.id){ res.render('signup', { message: "User Already Exists! Login or choose another user id"}); } }); var newUser = {id: req.body.id, password: req.body.password}; Users.push(newUser); req.session.user = newUser; res.redirect('/protected_page'); } }); function checkSignIn(req, res){ if(req.session.user){ next(); //If session exists, proceed to page } else { var err = new Error("Not logged in!"); console.log(req.session.user); next(err); //Error, trying to access unauthorized page! } } app.get('/protected_page', checkSignIn, function(req, res){ res.render('protected_page', {id: req.session.user.id}) }); app.get('/login', function(req, res){ res.render('login'); }); app.post('/login', function(req, res){ console.log(Users); if(!req.body.id || !req.body.password){ res.render('login', {message: "Please enter both id and password"}); } else { Users.filter(function(user){ if(user.id === req.body.id && user.password === req.body.password){ req.session.user = user; res.redirect('/protected_page'); } }); res.render('login', {message: "Invalid credentials!"}); } }); app.get('/logout', function(req, res){ req.session.destroy(function(){ console.log("user logged out.") }); res.redirect('/login'); }); app.use('/protected_page', function(err, req, res, next){ console.log(err); //User should be authenticated! Redirect him to log in. res.redirect('/login'); }); app.listen(3000);
我们创建了一个中间件函数 checkSignIn 来检查用户是否已登录。protected_page 使用此函数。要注销用户,我们需要销毁会话。
现在让我们创建登录页面。将视图命名为 login.pug 并输入内容 −
html head title Signup body if(message) h4 #{message} form(action = "/login" method = "POST") input(name = "id" type = "text" required placeholder = "User ID") input(name = "password" type = "password" required placeholder = "Password") button(type = "Submit") Log in
我们的简单身份验证应用程序现已完成;现在让我们测试该应用程序。使用 nodemon index.js 运行应用程序,然后转到 localhost:3000/signup。
输入用户名和密码,然后单击注册。如果详细信息有效/唯一,您将被重定向到 protected_page −
![Protected page](/expressjs/images/auth_protected.jpg)
现在退出应用程序。这会将我们重定向到登录页面 −
![Auth login](/expressjs/images/auth_login.jpg)
此路由受到保护,如果未经身份验证的人尝试访问它,他将被重定向到我们的登录页面。这都是关于基本用户身份验证的。始终建议我们使用持久会话系统并使用哈希进行密码传输。现在有更好的方法来验证用户身份,利用 JSON 令牌。
ExpressJS - RESTFul API
始终需要 API 来创建移动应用程序、单页应用程序、使用 AJAX 调用并向客户端提供数据。如何构造和命名这些 API 和端点的一种流行架构风格称为 REST(表述性传输状态)。HTTP 1.1 的设计考虑到了 REST 原则。REST 由 Roy Fielding 于 2000 年在他的 Paper Fielding Dissertations 中引入。
RESTful URI 和方法为我们提供了处理请求所需的几乎所有信息。下表总结了应如何使用各种动词以及如何命名 URI。我们将在最后创建一个电影 API;现在让我们讨论一下它将如何构建。
方法 | URI | 详细信息 | 函数 |
---|---|---|---|
GET | /movies | Safe, cachable | 获取所有电影及其详细信息的列表 |
GET | /movies/1234 | Safe, cachable | 获取电影 ID 1234 的详细信息 |
POST | /movies | N/A | 使用提供的详细信息创建新电影。响应包含此新创建资源的 URI。 |
PUT | /movies/1234 | Idempotent | 修改电影 ID 1234(如果不存在则创建一个)。响应包含此新创建资源的 URI。 |
DELETE | /movies/1234 | Idempotent | 如果存在影片 ID 1234,则应删除该影片。响应应包含请求的状态。 |
DELETE or PUT | /movies | Invalid | 应该无效。DELETE 和 PUT 应该指定它们正在处理的资源。 |
现在让我们在 Express 中创建此 API。我们将使用 JSON 作为传输数据格式,因为它在 JavaScript 中易于使用且具有其他优势。将您的 index.js 文件替换为 movies.js 文件,如以下程序所示。
index.js
var express = require('express'); var bodyParser = require('body-parser'); var multer = require('multer'); var upload = multer(); var app = express(); app.use(cookieParser()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(upload.array()); //需要我们在 movies.js 中定义的路由器 var movies = require('./movies.js'); //在子路由 /movies 上使用路由器 app.use('/movies', movies); app.listen(3000);
现在我们已经设置好了应用程序,让我们集中精力创建 API。
首先设置 movies.js 文件。我们不使用数据库来存储电影,而是将它们存储在内存中;因此每次服务器重新启动时,我们添加的电影都会消失。这可以很容易地使用数据库或文件(使用 node fs 模块)来模拟。
导入 Express 后,创建一个路由器并使用 module.exports 将其导出−
var express = require('express'); var router = express.Router(); var movies = [ {id: 101, name: "Fight Club", year: 1999, rating: 8.1}, {id: 102, name: "Inception", year: 2010, rating: 8.7}, {id: 103, name: "The Dark Knight", year: 2008, rating: 9}, {id: 104, name: "12 Angry Men", year: 1957, rating: 8.9} ]; //路由将在此处进行 module.exports = router;
GET 路由
让我们定义获取所有电影的 GET 路由 −
router.get('/', function(req, res){ res.json(movies); });
要测试这是否正常工作,请运行您的应用程序,然后打开您的终端并输入 −
curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET localhost:3000/movies
将显示以下响应 −
[{"id":101,"name":"Fight Club","year":1999,"rating":8.1}, {"id":102,"name":"Inception","year":2010,"rating":8.7}, {"id":103,"name":"The Dark Knight","year":2008,"rating":9}, {"id":104,"name":"12 Angry Men","year":1957,"rating":8.9}]
我们有一条获取所有电影的路线。现在让我们创建一条通过其 ID 获取特定电影的路线。
router.get('/:id([0-9]{3,})', function(req, res){ var currMovie = movies.filter(function(movie){ if(movie.id == req.params.id){ return true; } }); if(currMovie.length == 1){ res.json(currMovie[0]) } else { res.status(404);//由于未找到电影,因此将状态设置为 404 res.json({message: "Not Found"}); } });
这将根据我们提供的 id 获取电影。要检查输出,请在终端中使用以下命令 −
curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET localhost:3000/movies/101
您将获得以下响应 −
{"id":101,"name":"Fight Club","year":1999,"rating":8.1}
如果您访问无效路由,它将产生 无法 GET 错误,而如果您访问具有不存在的 id 的有效路由,它将产生 404 错误。
我们已经完成了 GET 路由,现在让我们继续讨论 POST 路由。
POST路由
使用以下路由处理POSTed数据 −
router.post('/', function(req, res){ //检查所有字段是否提供且有效: if(!req.body.name || !req.body.year.toString().match(/^[0-9]{4}$/g) || !req.body.rating.toString().match(/^[0-9]\.[0-9]$/g)){ res.status(400); res.json({message: "Bad Request"}); } else { var newId = movies[movies.length-1].id+1; movies.push({ id: newId, name: req.body.name, year: req.body.year, rating: req.body.rating }); res.json({message: "New movie created.", location: "/movies/" + newId}); } });
这将创建一个新电影并将其存储在 movies 变量中。要检查此路由,请在终端中输入以下代码 −
curl -X POST --data "name = Toy%20story&year = 1995&rating = 8.5" http://localhost:3000/movies
将显示以下响应 −
{"message":"New movie created.","location":"/movies/105"}
要测试这是否已添加到 movies 对象,请再次运行对 /movies/105 的 get 请求。将显示以下响应 −
{"id":105,"name":"Toy story","year":"1995","rating":"8.5"}
让我们继续创建 PUT 和 DELETE 路由。
PUT 路由
PUT 路由几乎与 POST 路由相同。我们将为将要更新/创建的对象指定 id。按以下方式创建路由。
router.put('/:id', function(req, res){ //检查所有字段是否提供且有效: if(!req.body.name || !req.body.year.toString().match(/^[0-9]{4}$/g) || !req.body.rating.toString().match(/^[0-9]\.[0-9]$/g) || !req.params.id.toString().match(/^[0-9]{3,}$/g)){ res.status(400); res.json({message: "Bad Request"}); } else { //获取具有给定 id 的电影的索引。 var updateIndex = movies.map(function(movie){ return movie.id; }).indexOf(parseInt(req.params.id)); if(updateIndex === -1){ //Movie not found, create new movies.push({ id: req.params.id, name: req.body.name, year: req.body.year, rating: req.body.rating }); res.json({message: "New movie created.", location: "/movies/" + req.params.id}); } else { //Update existing movie movies[updateIndex] = { id: req.params.id, name: req.body.name, year: req.body.year, rating: req.body.rating }; res.json({message: "Movie id " + req.params.id + " updated.", location: "/movies/" + req.params.id}); } } });
此路由将执行上表中指定的功能。如果对象存在,它将使用新详细信息更新对象。如果不存在,它将创建一个新对象。要检查路由,请使用以下 curl 命令。这将更新现有电影。要创建新电影,只需将 ID 更改为不存在的 ID。
curl -X PUT --data "name = Toy%20story&year = 1995&rating = 8.5" http://localhost:3000/movies/101
响应
{"message":"Movie id 101 updated.","location":"/movies/101"}
DELETE 路由
使用以下代码创建删除路由。 −
router.delete('/:id', function(req, res){ var removeIndex = movies.map(function(movie){ return movie.id; }).indexOf(req.params.id); //获取具有给定 id 的电影的索引。 if(removeIndex === -1){ res.json({message: "Not found"}); } else { movies.splice(removeIndex, 1); res.send({message: "Movie id " + req.params.id + " removed."}); } });
以与检查其他路线相同的方式检查路线。成功删除后(例如 id 105),您将获得以下输出 −
{message: "Movie id 105 removed."}
最后,我们的 movies.js 文件将如下所示。
var express = require('express'); var router = express.Router(); var movies = [ {id: 101, name: "Fight Club", year: 1999, rating: 8.1}, {id: 102, name: "Inception", year: 2010, rating: 8.7}, {id: 103, name: "The Dark Knight", year: 2008, rating: 9}, {id: 104, name: "12 Angry Men", year: 1957, rating: 8.9} ]; router.get('/:id([0-9]{3,})', function(req, res){ var currMovie = movies.filter(function(movie){ if(movie.id == req.params.id){ return true; } }); if(currMovie.length == 1){ res.json(currMovie[0]) } else { res.status(404); //由于未找到电影,因此将状态设置为 404 res.json({message: "Not Found"}); } }); router.post('/', function(req, res){ //检查所有字段是否提供且有效: if(!req.body.name || !req.body.year.toString().match(/^[0-9]{4}$/g) || !req.body.rating.toString().match(/^[0-9]\.[0-9]$/g)){ res.status(400); res.json({message: "Bad Request"}); } else { var newId = movies[movies.length-1].id+1; movies.push({ id: newId, name: req.body.name, year: req.body.year, rating: req.body.rating }); res.json({message: "New movie created.", location: "/movies/" + newId}); } }); router.put('/:id', function(req, res) { //检查所有字段是否提供且有效: if(!req.body.name || !req.body.year.toString().match(/^[0-9]{4}$/g) || !req.body.rating.toString().match(/^[0-9]\.[0-9]$/g) || !req.params.id.toString().match(/^[0-9]{3,}$/g)){ res.status(400); res.json({message: "Bad Request"}); } else { //获取具有给定 id 的电影的索引。 var updateIndex = movies.map(function(movie){ return movie.id; }).indexOf(parseInt(req.params.id)); if(updateIndex === -1){ //Movie not found, create new movies.push({ id: req.params.id, name: req.body.name, year: req.body.year, rating: req.body.rating }); res.json({ message: "New movie created.", location: "/movies/" + req.params.id}); } else { //Update existing movie movies[updateIndex] = { id: req.params.id, name: req.body.name, year: req.body.year, rating: req.body.rating }; res.json({message: "Movie id " + req.params.id + " updated.", location: "/movies/" + req.params.id}); } } }); router.delete('/:id', function(req, res){ var removeIndex = movies.map(function(movie){ return movie.id; }).indexOf(req.params.id); //获取具有给定 id 的电影的索引。 if(removeIndex === -1){ res.json({message: "Not found"}); } else { movies.splice(removeIndex, 1); res.send({message: "Movie id " + req.params.id + " removed."}); } }); module.exports = router;
这完成了我们的 REST API。现在,您可以使用这种简单的架构风格和 Express 创建更复杂的应用程序。
ExpressJS - 脚手架
脚手架使我们能够轻松地为 Web 应用程序创建骨架。我们手动创建公共目录、添加中间件、创建单独的路由文件等。脚手架工具为我们设置了所有这些内容,以便我们可以直接开始构建应用程序。
我们将使用的脚手架称为Yeoman。它是为Node.js构建的脚手架工具,但也有其他几个框架(如 flask、rails、django 等)的生成器。要安装 Yeoman,请在终端中输入以下命令 −
npm install -g yeoman
Yeoman 使用生成器来构建应用程序。要查看 npm 上可用的生成器以与 Yeoman 一起使用,您可以单击此 链接。在本教程中,我们将使用 'generator-Express-simple'。要安装此生成器,请在终端中输入以下命令 −
npm install -g generator-express-simple
要使用此生成器,请输入以下命令 −
yo express-simple test-app
系统会询问您几个简单的问题,例如您想在应用中使用哪些内容。请选择以下答案,或者如果您已经了解这些技术,则选择您希望它们如何实现。
express-simple comes with bootstrap and jquery [?] Select the express version you want: 4.x [?] Do you want an mvc express app: Yes [?] Select the css preprocessor you would like to use: sass [?] Select view engine you would like to use: jade [?] Select the build tool you want to use for this project: gulp [?] Select the build tool you want to use for this project: gulp [?] Select the language you want to use for the build tool: javascript create public/sass/styles.scss create public/js/main.js create views/layout.jade create views/index.jade create views/404.jade create app.js create config.js create routes/index.js create package.json create bower.json identical .bowerrc identical .editorconfig identical .gitignore identical .jshintrc create gulpfile.js I'm all done. Running bower install & npm install for you to install the required dependencies. If this fails, try running the command yourself.
然后它将为您创建一个新应用程序,安装所有依赖项,向您的应用程序添加一些页面(主页、404 未找到页面等),并为您提供一个目录结构以供使用。
此生成器为我们创建了一个非常简单的结构。探索 Express 可用的许多生成器,并选择最适合您的生成器。使用所有生成器的步骤是相同的。您需要安装一个生成器,使用 Yeoman 运行它;它会问您一些问题,然后根据您的答案为您的应用程序创建一个框架。
ExpressJS - 错误处理
Express 中的错误处理是使用中间件完成的。但这个中间件具有特殊属性。错误处理中间件的定义方式与其他中间件函数相同,只是错误处理函数必须有四个参数而不是三个 - err、req、res、next。例如,要发送任何错误的响应,我们可以使用 −
app.use(function(err, req, res, next) { console.error(err.stack); res.status(500).send('Something broke!'); });
到目前为止,我们一直在路由本身中处理错误。错误处理中间件允许我们分离错误逻辑并相应地发送响应。我们在中间件中讨论的 next() 方法将我们带到下一个 中间件/路由处理程序。
对于错误处理,我们有 next(err) 函数。调用此函数会跳过所有中间件,并将我们匹配到该路由的下一个错误处理程序。让我们通过一个例子来理解这一点。
var express = require('express'); var app = express(); app.get('/', function(req, res){ //创建一个错误并将其传递给下一个函数 var err = new Error("Something went wrong"); next(err); }); /* * 其他路由处理程序和中间件在这里 * .... */ //错误处理中间件 app.use(function(err, req, res, next) { res.status(500); res.send("Oops, something went wrong.") }); app.listen(3000);
此错误处理中间件可以策略性地放置在路由之后或包含条件来检测错误类型并相应地响应客户端。上述程序将显示以下输出。
![错误处理](/expressjs/images/error_handling.jpg)
ExpressJS - 调试
Express 使用 Debug 模块在内部记录有关路由匹配、中间件功能、应用程序模式等的信息。
要查看 Express 中使用的所有内部日志,请在启动应用程序时将 DEBUG 环境变量设置为 Express:* −
DEBUG = express:* node index.js
将显示以下输出。
![调试日志](/expressjs/images/debugging.jpg)
这些当您的应用程序组件无法正常运行时,日志非常有用。这种冗长的输出可能有点让人不知所措。您还可以将 DEBUG 变量限制到要记录的特定区域。例如,如果您希望将记录器限制到应用程序和路由器,则可以使用以下代码。
DEBUG = express:application,express:router node index.js
默认情况下,调试处于关闭状态,在生产环境中会自动打开。调试还可以扩展以满足您的需求,您可以在其 npm 页面上阅读更多相关信息。
ExpressJS - 最佳实践
与具有定义的方式、文件结构等的 Django 和 Rails 不同,Express 不遵循定义的方式。这意味着您可以按照自己喜欢的方式构建应用程序。但是,随着应用程序规模的增长,如果没有明确定义的结构,维护起来会非常困难。在本章中,我们将研究构建应用程序时常用的目录结构和关注点分离。
首先,我们将讨论创建节点和 Express 应用程序的最佳实践。
始终使用 npm init 开始节点项目。
始终使用 --save 或 --save-dev 安装依赖项。这将确保如果您移动到其他平台,只需运行 npm install 即可安装所有依赖项。
坚持使用小写文件名和驼峰式变量。如果您查看任何 npm 模块,其名称都以小写字母命名并用破折号分隔。每当您需要这些模块时,请使用 camelCase。
不要将 node_modules 推送到您的存储库。相反,npm 会在开发机器上安装所有内容。
使用 config 文件存储变量
将路由分组并隔离到它们自己的文件中。例如,以我们在 REST API 页面中看到的电影示例中的 CRUD 操作为例。
目录结构
现在让我们讨论 Express 的目录结构。
网站
Express 没有用于创建应用程序的社区定义结构。以下是网站主要使用的项目结构。
test-project/ node_modules/ config/ db.js //数据库连接及配置 credentials.js //您的应用使用的外部服务的密码/API 密钥 config.js //其他环境变量 models/ //对于 mongoose 模式 users.js things.js routes/ //不同文件中不同实体的所有路由 users.js things.js views/ index.pug 404.pug ... public/ //正在提供所有静态内容 images/ css/ javascript/ app.js routes.js // 要求此处包含所有路由,然后要求此文件 app.js package.json
还有其他方法可以使用 Express 构建网站。您可以使用 MVC 设计模式构建网站。欲了解更多信息,请访问以下链接。
https://code.tutsplus.com/tutorials/build-a-complete-mvc-website-with-expressjs--net-34168
并且,
https://www.terlici.com/2014/08/25/best-practices-express-structure.html。
RESTful API
API 设计起来更简单;它们不需要 public 或 views 目录。使用以下结构来构建 API −
test-project/ node_modules/ config/ db.js //数据库连接及配置 credentials.js //您的应用使用的外部服务的密码/API 密钥 models/ //对于 mongoose 模式 users.js things.js routes/ //不同文件中不同实体的所有路由 users.js things.js app.js routes.js // 要求此处包含所有路由,然后要求此文件 app.js package.json
您还可以使用 yeoman 生成器 来获得类似的结构。
ExpressJS - 资源
本章列出了我们用于本教程的各种资源。
最重要的链接当然是 Express API 文档 − https://expressjs.com/en/4x/api.html
Express 网站上提供的关于不同方面的指南也非常有帮助 −
Express 最常用的中间件列表可在 https://expressjs.com/en/resources/middleware.html
上找到。
这些包含 Express 技巧和窍门的博客可能会有所帮助 −
应用程序结构 − https://www.terlici.com/2014/08/25/best-practices-express-structure.html
RESTful API −
https://www.thepolyglotdeveloper.com/2015/10/create-a-simple-restful-api-with-node-js/
https://scotch.io/tutorials/build-a-restful-api-using-node-and-express-4
https://pixelhandler.com/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose
http://cwbuecheler.com/web/tutorials/2014/restful-web-app-node-express-mongodb/
对于高级身份验证,请使用 PassportJS − http://passportjs.org