Express チュートリアル Part 6: フォームの操作

この記事は翻訳が完了していません。 この記事の翻訳にご協力ください

本教程将向您展示如何使用Pug在Express中处理HTML表单,特别是如何创建用于创建,更新和删除数据库文档的表单.

前提条件: Express チュートリアル Part 5: ライブラリデータの表示など、これまでのチュートリアルのトピックをすべて完了してください。
目標: ユーザからデータを取得するためのフォームの作成方法を理解し、このデータでデータベースを更新する。

Overview

HTML表单网页上的一组一个或多个字段/小组件,可用于从用户收集信息以提交到服务器. 表单是一种用于收集用户输入的灵活机制,因为可以使用适当的表单输入来输入许多不同类型的数据,例如文本框,复选框,单选按钮,日期选择器等.表单也是与服务器共享数据的相对安全的方式,因为它们允许我们在具有跨站点请求伪造保护的POST请求中发送数据.

使用表单可能会很复杂! 开发人员需要为表单编写HTML,在服务器上(可能还有在浏览器中)验证并正确清理输入的数据,使用错误消息重新发布表单以通知用户任何无效字段,并在成功提交数据后处理数据,最后以某种方式回应用户以表示成功.

在本教程中,我们将向您展示如何在Express中执行上述操作. 在此过程中,我们将扩展LocalLibrary网站,以允许用户创建,编辑和删除库中的项目.

注意:我们还没有研究如何将特定路由限制到经过身份验证或授权的用户,因此,在这一点上,任何用户都可以对数据库进行更改.

HTML Forms

首先简要概述HTML表单 . 考虑一个简单的HTML表单,该表单具有用于输入某些"团队"名称及其相关标签的单个文本字段:

Simple name field example in HTML form

表单在HTML中定义为<form>...</form>标记内的元素的集合,其中至少包含type="submit" input元素.

<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属性定义将显示哪种小部件. 字段的nameid用于标识JavaScript / CSS / HTML中的字段,而value定义字段在首次显示时的初始值. 匹配的团队标签是使用label标签指定的(请参见上面的"输入名称"),其中的for字段包含相关inputid值.

submit输入将显示为一个按钮(默认情况下)—用户可以按此按钮以将其他输入元素包含的数据上载到服务器(在这种情况下,仅是team_name ). 表单属性定义用于发送数据的HTTP method以及服务器上数据的目的地( action ):

  • action :提交表单时要在其中发送数据进行处理的资源/ URL. 如果未设置(或设置为空字符串),则表单将被提交回当前页面URL.
  • method :用于发送数据的HTTP方法: POSTGET .
    • 如果数据将导致服务器数据库的更改,则应始终使用POST方法,因为这样可以使它更能抵抗跨站点的伪造请求攻击.
    • GET方法仅应用于不更改用户数据的表单(例如搜索表单). 建议您在希望添加书签或共享URL时使用.

Form handling process

表单处理使用我们学到的用于显示有关模型信息的所有相同技术:路由将请求发送到控制器功能,该控制器功能执行所需的任何数据库操作,包括从模型中读取数据,然后生成并返回HTML页面. 使事情变得更加复杂的是,服务器还需要能够处理用户提供的数据,并在出现任何问题时重新显示带有错误信息的表格.

下面显示了用于处理表单请求的过程流程图,该流程图从对包含表单的页面的请求(以绿色显示)开始:

如上图所示,表单处理代码需要做的主要事情是:

  1. 在用户第一次请求时显示默认表单.
    • 该表单可能包含空白字段(例如,如果您正在创建新记录),或者可能会预先填充有初始值(例如,如果您正在更改记录或具有有用的默认初始值).
  2. 通常在HTTP POST请求中接收用户提交的数据.
  3. 验证和清理数据.
  4. 如果任何数据无效,请重新显示该表单,这一次是使用用户填充的值和问题字段的错误消息.
  5. 如果所有数据均有效,请执行所需的操作(例如,将数据保存在数据库中,发送通知电子邮件,返回搜索结果,上传文件等)
  6. 完成所有操作后,将用户重定向到另一个页面.

通常,表单处理代码是使用GET路由来实现表单的初始显示,而使用POST路由来实现相同的路径,以处理表单数据的验证和处理. 这是本教程将使用的方法!

Express本身不为表单处理操作提供任何特定的支持,但是它可以使用中间件来处理表单中的POSTGET参数,以及验证/清除其值.

Validation and sanitization

在存储表单中的数据之前,必须先对其进行验证和清理:

  • 验证检查输入的值是否适合每个字段(在正确的范围,格式等范围内),以及是否已为所有必填字段提供了值.
  • 清理会删除/替换数据中可能用于向服务器发送恶意内容的字符.

在本教程中,我们将使用流行的express-validator模块执行表单数据的验证和清理.

Installation

通过在项目的根目录中运行以下命令来安装模块.

npm install express-validator

Using 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等的数据,也可以一次检查所有这些数据. 在本教程中,我们将主要使用bodysanitizeBodyvalidationResult (如上"必填").

功能定义如下:

  • body(fields[, message]) :指定请求正文中的一组字段(一个POST参数),以进行验证以及可选的错误消息,如果测试失败,则会显示该错误消息. 验证标准以菊花链方式链接到body()方法. 例如,下面的第一项检查测试"名称"字段是否为空,如果不是,则设置错误消息"空名称". 第二项测试检查age字段是否为有效日期,并使用optional()指定null和空字符串不会使验证失败.
      body('name','Empty name').isLength({min:1}), 
     body('age','Invalid age').optional({checkFalsy:true}).isISO8601(),
    
    您还可以以菊花链方式链接不同的验证器,并添加前面验证器为true时显示的消息.
      body('name').isLength({min:1}).trim().withMessage('Name empty.')
         .isAlpha().withMessage('名称必须为字母.'),
    

    注意:您还可以添加内嵌式消毒器,如trim() ,如上所示. 但是,此处应用的消毒剂仅适用于验证步骤. 如果要清理最终输出,则需要使用单独的清理方法,如下所示.

  • sanitizeBody(fields) :指定要清理的主体字段. 然后将消毒操作以菊花链方式连接到该方法. 例如,下面的escape()清理操作从name变量中删除了可能在JavaScript跨站点脚本攻击中使用的HTML字符.
      sanitizeBody('name').trim().escape(),
     sanitizeBody('date').toDate(), 
  • validationResult(req) :运行验证,使错误以validation结果对象的形式出现. 这是在单独的回调中调用的,如下所示:
      (要求,要求,下一项)=> {
     //从请求中提取验证错误.
     常量错误= validationResult(req);
    
     如果(!errors.isEmpty()){
     //有错误.  再次使用已清理的值/错误消息呈现表单.
     //使用`errors.array()`可以在数组中返回错误消息.
     }
     其他{
     //来自表单的数据有效.
     }
     } 
    我们使用验证结果的isEmpty()方法检查是否存在错误,并使用其array()方法获取错误消息集. 有关更多信息,请参见Validation Result API .

验证和消毒链是应该传递给Express路由处理程序的中间件(我们通过控制器间接进行此操作). 当中间件运行时,每个验证器/清除器都以指定的顺序运行.

当我们实现下面的LocalLibrary表单时,我们将介绍一些真实的示例.

Form design

库中的许多模型都是相关/相关的,例如,一Book 需要 Author ,也可能具有一个或多个Genres . 这就提出了一个问题,当用户希望:

  • 当其相关对象尚不存在时创建一个对象(例如,尚未定义作者对象的书).
  • 删除另一个对象仍在使用的对象(例如,删除仍在Book中使用的Genre ).

对于此项目,我们将通过声明表单只能执行以下操作来简化实现:

  • 使用已经存在的对象创建一个对象(因此用户在尝试创建任何Book对象之前必须创建任何必需的AuthorGenre实例).
  • 如果某个对象未被其他对象引用,则将其删除(例如,在删除所有关联的BookInstance对象之前,您将无法删除Book ).

注意:更"可靠"的实现可能允许您在创建新对象时创建从属对象,并随时删除任何对象(例如,通过删除从属对象,或从数据库中删除对已删除对象的引用) .

Routes

为了实现我们的表单处理代码,我们将需要两条具有相同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 forms subarticles

以下子文章将引导我们完成将所需表单添加到示例应用程序的过程. 在继续进行下一个之前,您需要依次阅读和阅读每个内容.

  1. "创建类型"表单 -定义页面以创建Genre对象.
  2. Create Author form — Defining a page to create Author objects.
  3. 创建书本表单 —定义页面/表单以创建Book对象.
  4. 创建BookInstance表单 -定义一个页面/表单以创建BookInstance对象.
  5. 删除作者表单 -定义一个页面以删除Author对象.
  6. 更新书本表单 —定义页面以更新Book对象.

Challenge yourself

实现BookBookInstanceGenre模型的删除页面,以与我们的Author delete页面相同的方式从关联的详细信息页面链接它们. 页面应采用相同的设计方法:

  • 如果存在其他对象对该对象的引用,则这些其他对象应与注释一起显示,除非删除列出的对象,否则该记录不能删除.
  • 如果没有对该对象的其他引用,则该视图应提示删除它. 如果用户按Delete键,则应删除记录.

一些提示:

  • 删除Genre就像删除Author一样,因为这两个对象都是Book依赖项(因此,在两种情况下,只有在删除关联的图书时,才可以删除对象).
  • 删除一Book也很相似,但是您需要检查是否没有关联的BookInstances .
  • 删除BookInstance最简单,因为没有依赖对象. 在这种情况下,您只需找到关联的记录并将其删除.

Implement the update pages for the BookInstance, Author, and Genre models, linking them from the associated detail pages in the same way as our 图书更新 page.

一些提示:

  • 我们刚刚实施的" 图书更新"页面最难! 相同的模式可用于其他对象的更新页面.
  • Author死亡日期和出生日期字段以及BookInstance Due_date字段是错误的格式,无法输入到表单的日期输入字段中(它需要格式为" YYYY-MM-DD"的数据). 解决此问题的最简单方法是为日期定义一个新的虚拟属性,以适当地格式化日期,然后在关联的视图模板中使用此字段.
  • 如果您遇到困难,可以在此处的示例中找到更新页面的示例 .

まとめ

NPM的Express,Node和第三方软件包提供了向网站添加表单所需的一切. 在本文中,您学习了如何使用Pug创建表单,使用express-validator验证和清理输入,以及如何在数据库中添加,删除和修改记录.

现在,您知道如何向Node网站添加基本表单和表单处理代码.

あわせて参照

このモジュール