Express 教程 6: 使用表单
在此教程中我们会教你如何使用 Express 并且结合 Pug 来实现 HTML 表单,并且如何从数据库中创建,更新和删除文档。
前提: |
完成前面所有的教程,包括 Express 教程第 5 章:展示图书馆数据 |
---|---|
目标: |
了解如何编写表单获取用户信息,并且将这些数据更新到数据库中。 |
概览
HTML 表单在网页中是一个或多个字段/小工具的集合,它被用来收集用户的信息,并将信息上传到服务器。表单作为一种用来收集用户的机制,非常的灵活,因为有各种合适的输入框,来接受各种类型的数据——文本框,复选框,单选按钮,时间选择器等。表单和服务器交互数据也相对安全,因为它使用 POST 请求发送数据,保护不受跨站点请求伪造攻击(cross-site request forgery)的威胁。
但是表单同样也很复杂!开发者需要为表单编写 HTML,在服务器上验证,并且正确去除有害的数据(浏览器上也可能需要),对于任何不合法的字段,需要传给用户相应的错误信息,当数据成功提交后,处理数据,并设法通知用户提交成功。
此教程将展示上述的操作,如何在 Express 中实现。在此过程中,我们将扩展 LocalLibrary 网站,以允许用户创建,编辑和删除图书馆中的项目。
备注:我们还没有考虑如何将特定路由,限制为经过身份验证或授权的用户,因此在这个时间点,任何用户都可以对数据库进行更改。
HTML 表单
首先简要概述HTML 表单。考虑一个简单的 HTML 表单,其中包含一个文本字段,用于输入某些“团队”的名称,及其相关标签:
表单在 HTML 中,定义为<form>...</form>
标记内的元素集合,包含至少一个type="submit"
的input
输入元素。
请注意,我非常建议你这里使用 input 的 submit 而不是 button!这会使你感到愉悦。
<form action="/team_name_url/" method="post">
<label for="team_name">Enter name: </label>
<input
id="team_name"
type="text"
name="name_field"
value="Default name for team." />
<input type="submit" value="OK" />
</form>
虽然这里,我们只包含一个(文本)字段,用于输入团队名称,但表单可能包含任意数量的其他输入元素,及其相关标签。字段的 type
属性,定义将显示哪种窗口小部件。该字段的名称 name
和 id
,用于标识 JavaScript/CSS/HTML 中的字段,而 value
定义字段首次显示时的初始值。匹配团队标签使用 label
标签,指定(请参阅上面的“输入名称” "Enter name"),其中 for
字段,包含 input
相关输入的 id
值。
另外,有必要说一下,HTML 中 form 表单默认就是以 post 提交的。它比 get 方式存储量更大、传输更安全。
提交输入(submit
)将显示为按钮(默认情况下) - 用户可以按此按钮,将其他输入元素包含的数据,上传到服务器(在本例中,只有team_name
)。表单属性,定义用于发送数据的 HTTP method
方法,和服务器上数据的目标(action
):
action
: 提交表单时,要发送数据以进行处理的资源/ URL。如果未设置(或设置为空字符串),则表单将提交回当前页面 URL。method
: 用于发送数据的 HTTP 方法:POST
或GET
。- 如果数据将导致服务器数据库的更改,则始终应该使用
POST
方法,因为这更加可以抵抗跨站点伪造请求攻击。 GET
方法只应用于不更改用户数据的表单(例如,搜索表单)。当你希望能够为 URL 添加书签或共享时,建议使用此选项。
- 如果数据将导致服务器数据库的更改,则始终应该使用
表单处理流程
表单处理使用的技术,与我们学习过、用来显示有关模型的信息的所有技术,是相同的:路由将我们的请求发送到控制器函数,该函数执行所需的任何数据库操作,包括从模型中读取数据,然后生成并返回 HTML 页面。使事情变得更复杂的是,服务器还需要能够处理用户提供的数据,并在出现任何问题时,重新显示带有错误信息的表单。
下面显示了处理表单请求的流程图,从包含表单的页面请求开始(以绿色显示):
如上图所示,构成处理代码所需要做的主要是:
- 在用户第一次请求时显示默认表单。
- 表单可能包含空白字段(例如,如果你正在创建新记录),或者可能预先填充了初始值(例如,如果你要更改记录,或者具有有用的默认初始值)。
- 接收用户提交的数据,通常是在 HTTP
POST
请求中。 - 验证并清理数据。
- 如果任何数据无效,请重新显示表单 - 这次使用用户填写的任何值,和问题字段的错误消息。
- 如果所有数据都有效,请执行所需的操作(例如,将数据保存在数据库中,发送通知电子邮件,返回搜索结果,上传文件等)
- 完成所有操作后,将用户重定向到另一个页面。
表格处理代码,通常使用GET
路由,以实现表单的初始显示,以及POST
路由到同一路径,以处理表单数据的验证和处理。这是将在本教程中使用的方法!Express 本身不提供表单处理操作的任何特定支持,但它可以使用中间件,以处理表单中的POST
和GET
参数,并验证/清理它们的值。
验证和清理
在储存表单中的数据之前,必须对其进行验证和清理:
- 验证检查输入的值,适用于每个字段(范围,格式等),并且已为所有必填字段提供了值。
- 清理删除/替换数据中的字符,可能用于将恶意内容发送到服务器。
在本教程中,我们将使用流行的 express-validator 模块,来执行表单数据的验证和清理。
安装
通过在项目的根目录中,运行以下命令来安装模块。
npm install express-validator --save
使用 express-validator
备注:Github 上的express-validator指南,提供了 API 的良好概述。我们建议你阅读该内容,以了解其所有功能(包括创建自定义验证程序)。下面我们只介绍一个对 LocalLibrary 有用的子集。
要在我们的控制器中使用验证器,我们必须从'express-validator/check'和'express-validator/filter'模块中,导入我们想要使用的函数,如下所示:
const { body, validationResult } = require("express-validator/check");
const { sanitizeBody } = require("express-validator/filter");
有许多可用的功能,允许你一次检查和清理请求参数,正文,标头,cookie 等数据,或所有数据。对于本教程,我们主要使用body
, sanitizeBody
,和 validationResult
(如上面“导入”的)。
功能定义如下:
body(fields[, message])
: 指定请求本文中的一组字段(POST
参数)以及可选的错误消息,如果测试失败,则可以显示该字段。验证标准以菊花链形式连接到body()
方法。例如,下面的第一个检查测试,“name”字段不为空,如果不是,则设置错误消息“Empty name”。第二个测试,检查 age 字段是否为有效日期,并使用optional()
指定 null 和空字符串不会验证失败。你还可以用菊花链式连接不同的验证器,并添加前面验证器为真时显示的消息。jsbody('name', 'Empty name').isLength({ min: 1 }), body('age', 'Invalid age').optional({ checkFalsy: true }).isISO8601(),
jsbody('name').isLength({ min: 1 }).trim().withMessage('Name empty.') .isAlpha().withMessage('Name must be alphabet letters.'),
备注:
你还可以添加内联清理器,如
trim()
,如上所示。但是,此处应用清理器,仅适用于验证步骤。如果要对最终输出进行消毒,则需要使用单独的清理器方法,如下所示。sanitizeBody(fields)
: 指定一个正文要清理的字段。然后将清理操作,以菊花链形式连接到此方法。例如,下面的escape()
清理操作,会从名称变量中,删除可能在 JavaScript 跨站点脚本攻击中使用的 HTML 字符。jssanitizeBody('name').trim().escape(), sanitizeBody('date').toDate(),
validationResult(req)
: 运行验证,以validation
验证结果对象的形式,提供错误。这是在单独的回调中调用的,如下所示:我们使用验证结果的js(req, res, next) => { // Extract the validation errors from a request. const errors = validationResult(req); if (!errors.isEmpty()) { // There are errors. Render form again with sanitized values/errors messages. // Error messages can be returned in an array using `errors.array()`. } else { // Data from form is valid. } };
isEmpty()
方法,来检查是否存在错误,并使用其array()
方法,来获取错误消息集合。有关更多信息,请参阅验证结果 API。
验证和清理链,是应该传递给 Express 路由处理程序的中间件(我们通过控制器,间接地执行此操作)。中间件运行时,每个验证器/清理程序都按指定的顺序运行。 当我们实现下面的 LocalLibrary 表单时,我们将介绍一些真实的例子。
表单设计
图书馆中的许多模型都是相关/依赖的 - 例如,一本书需要一个作者,也可能有一个或多个种类。这提出了一个问题,即我们应该如何处理用户希望的情况:
- 在其相关对象尚不存在时,创建对象(例如,尚未定义作者对象的书)。
- 删除另一个对象仍在使用的对象(例如,删除仍有书本使用的种类)。
在这个项目,我们将简单声明表单只能:
- 使用已存在的对象创建对象(因此用户在尝试创建任何
Book
对象之前,必须创建任何所需的Author
和Genre
实例)。 - 如果对象未被其他对象引用,则删除该对象(例如,在删除所有关联的
BookInstance
对象之前,你将无法删除该书)。
让我们看看更高级的内容吧:
我们通常会在“后台”接收 form 表单提交的数据。显而易见,这里应该是 express!
首先我们可以知道(也许你会知道)应该先引入 express:
const app=express();
这很好。
那么既然是 post 提交,给大家推荐一款中间件:body-parser。它能让你轻松地处理 body 数据。
哦,如果你涉及文件上传,那么你可能需要“multer”中间件,你大概听说过“formidable”,但 multer 比它更强大!
备注:更“牢固”的实现,可能允许你在创建新对象时创建依赖对象,并随时删除任何对象(例如,通过删除依赖对象,或从数据库中,删除对已删除对象的引用) 。
路由
为了实现我们的表单处理代码,我们需要两个具有相同 URL 模式的路由。
第一个(GET
)路由,用于显示用于创建对象的新空表单。第二个路由(POST
),用于验证用户输入的数据,然后保存信息,并重定向到详细信息页面(如果数据有效),或重新显示有错误的表单(如果数据无效)。
我们已经在 /routes/catalog.js(在之前的教程中)为我们所有模型的创建页面,创建了路径。例如,种类路由如下所示:
// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id).
router.get("/genre/create", genre_controller.genre_create_get);
// POST request for creating Genre.
router.post("/genre/create", genre_controller.genre_create_post);
Express 表单子文件
挑战自我
实现Book
, BookInstance
, 和 Genre
模型的删除页面,以与我们的作者删除页面相同的方式,将它们与关联的详细信息页面,链接起来。页面应遵循相同的设计方法:
- 如果有来自其他对象的、对于对象的引用,则应显示注释,列出这些对象,并说明在删除列出的对象之前,无法删除此记录。
- 如果没有对该对象的其他引用,则视图应提示删除它。如果用户按下“删除”按钮,则应删除该记录。
一些提示:
- 删除种类
Genre
就像删除作者Author
一样,因为两个对象都是Book
的依赖项(因此在这两种情况下,只有在删除相关书本时,才能删除对象)。 - 删除书本
Book
也很相似,但你需要检查是否没有关联的书本实例BookInstances
。 - 删除书本实例
BookInstance
是最简单的,因为没有依赖对象。在这种情况下,你只需找到相关记录并将其删除即可。
实现BookInstance
, Author
, 和 Genre
模型的更新页面,以与我们的书本更新页面相同的方式,将它们与关联的详细信息页面,链接起来。
一些提示:
- 我们刚刚实施的图书更新页面是最难的!相同的模式可用于其他对象的更新页面。
- 作者死亡日期和出生日期字段以及书本实例 due_date 字段,是输入到表单上日期输入字段的错误格式(它需要“YYYY-MM-DD”形式的数据)。解决此问题的最简单方法,是为适当格式化的日期,定义新的虚拟属性,然后在关联的视图模板中,使用此字段。
- 如果你遇到困难,此处示例中的更新页面有一些示例。
总结
Express, node, 与 NPM 上面的第三方套件,提供你需要的每样东西,可用于新增表单到你的网站上。在本文中,你学到了如何使用 Pug, how to create forms using Pug, validate and sanitize input using express-validator, and add, delete, and modify records in the database.
你现在应该了解如何新增基本表单,以及表单处理码到你的 node 网站!
请见
- express-validator (npm 文档).