Esta traducción está incompleta. Por favor, ayuda a traducir este artículo del inglés.

En este tutorial te mostraremos como trabajar con Formularios HTML en Express usando Pug. En particular, como crear, actualizar, y borrar registos de la base de datos utilizando el Formilario propio de HTML.

Requisitos previos: Completar los tutoriales previos incluyendo: Express Tutorial Part 5: Displaying library data
Objectivo: Entender como escribir formularios y así recibir información de usuarios, actualizando la base de datos con los mismos.

Overview

Un Formulario HTML es un grupo de uno o mas campos/fields en una pagina web que pueden ser usados para recolectar informacion/data de usuarios. Estos datos seran procesados del lado del servidor. Los formularios son un mecanismo muy flexible a la hora de ser utilizado para recolectar los inputs del usuario, ya que estos pueden cambiar su tipo de entrada y asi adecuarse al ingreso de dato previsto — Text Boxs, Check Box, Radio Buttons, Date Pickers, etc. Los formularios tambien son una forma relativamente segura de compartir datos con el servidor, ya que nos permite enviar datos mediante solicitudes POST (los famosos Post Requests) utilizando tambien la proteccion del sitio.

¡Trabajar con formularios puede ser complicado! Los developers necesitan escribir el codigo en HTML, validar y limpiar apropiadamente el dato que será enviado al servidor. Una buena practica es re-postear el formulario con los mensajes de error en caso de que la data no haya sido bien depurada. Con esto informamos al usuario sobre algun campo no completado o algun campo invalido. Otro objeto es manejar la data cuando se ha logrado enviar con exito y a su vez informar al usuario que la carga se ha realizado con éxito.

En este tutorial vamos a mostrarte  como las operaciones anteriores pueden ser realizadas en Express. A lo largo del camino manejaremos la LocalLibrary de la web para permitir al usuario crear, editar y borrar items de la libreria.

Nota: No hemos visto como restringir rutas particulares para que sean usadas solo por usuarios autenticados o autorizados. Si no hacemos esto cualquier usuario esta habilitado a hacer cambios a la base de datos. ¡Esto es altamente riesgoso!

Formularios HTML

Primero, hagamos una vista general de lo que es un Formulario HTML. Consideremos un formulario HTML simple, con un solo campo de texto <input> para ingresar el nombre del equipo de futbol, y su <label> asociada:

Simple name field example in HTML form

El formulario esta definido en HTML como una coleccion de elementos dentro del tag <form>...</form> , a su vez contiene un elemento input con su respectivo tipo type="submit".

<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>

Mientras que aquí solo hemos incorporado un elemento, un formulario podria contener muchos elementos que pueden o no estar asociados a una o mas Labels. Los campos de atributo type definirán que tipo de widget serán mostrados. El name y el id son usados para identificar el field en JavaScript/CSS/HTML, mientras que value define el valor inicial para el campo cuando este es mostrado por primera vez. Para la coincidencia de elementos entre el label tag (observar "Enter name" ), utilizamos el atributo for que contiene el mismo parametro que el atributo id asociado al input.

The submit input will be displayed as a button (by default)—this can be pressed by the user to upload the data contained by the other input elements to the server (in this case, just the team_name). The form attributes define the HTTP method used to send the data and the destination of the data on the server (action):

  • action: The resource/URL where data is to be sent for processing when the form is submitted. If this is not set (or set to an empty string), then the form will be submitted back to the current page URL.
  • method: The HTTP method used to send the data: POST or GET.
    • The POST method should always be used if the data is going to result in a change to the server's database, because this can be made more resistant to cross-site forgery request attacks.
    • The GET method should only be used for forms that don't change user data (e.g. a search form). It is recommended for when you want to be able to bookmark or share the URL.

Form handling process

Form handling uses all of the same techniques that we learned for displaying information about our models: the route sends our request to a controller function which performs any database actions required, including reading data from the models, then generates and returns an HTML page. What makes things more complicated is that the server also needs to be able to process data provided by the user, and redisplay the form with error information if there are any problems.

A process flowchart for processing form requests is shown below, starting with a request for a page containing a form (shown in green):

As shown in the diagram above, the main things that form handling code needs to do are:

  1. Display the default form the first time it is requested by the user.
    • The form may contain blank fields (e.g. if you're creating a new record), or it may be pre-populated with initial values (e.g. if you are changing a record, or have useful default initial values).
  2. Receive data submitted by the user, usually in an HTTP POST request.
  3. Validate and sanitize the data.
  4. If any data is invalid, re-display the form—this time with any user populated values and error messages for the problem fields.
  5. If all data is valid, perform required actions (e.g. save the data in the database, send a notification email, return the result of a search, upload a file, etc.)
  6. Once all actions are complete, redirect the user to another page.

Often form handling code is implemented using a GET route for the initial display of the form and a POST route to the same path for handling validation and processing of form data. This is the approach that will be used in this tutorial!

Express itself doesn't provide any specific support for form handling operations, but it can use middleware to process POST and GET parameters from the form, and to validate/sanitize their values.

Validation and sanitization

Before the data from a form is stored it must be validated and sanitized:

  • Validation checks that entered values are appropriate for each field (are in the right range, format, etc.) and that values have been supplied for all required fields.
  • Sanitization removes/replaces characters in the data that might potentially be used to send malicious content to the server.

For this tutorial we'll be using the popular express-validator module to perform both validation and sanitization of our form data.

Installation

Install the module by running the following command in the root of the project.

npm install express-validator

Using express-validator

Note: The express-validator guide on  Github provides a good overview of API. We recommend you read that to get an idea of all its capabilities (including creating custom validators). Below we cover just a subset that is useful for the LocalLibrary.

To use the validator in our controllers we have to require the functions we want to use from the 'express-validator/check' and 'express-validator/filter' modules, as shown below:

const { body,validationResult } = require('express-validator/check');
const { sanitizeBody } = require('express-validator/filter');

There are many functions available, allowing you to check and sanitize data from request parameters, body, headers, cookies, etc., or all of them at once. For this tutorial, we'll primarily be using bodysanitizeBody, and validationResult (as "required" above).

The functions are defined as below:

  • body(fields[, message]): Specifies a set of fields in the request body (a POST parameter) to validate along with an optional error message that can be displayed if it fails the tests. The validation criteria are daisy-chained to the body() method. For example, the first check below tests that the "name" field is not empty and sets an error message "Empty name" if it is not. The second test checks that the age field is a valid date, and using optional() to specify that null and empty strings will not fail validation.
    body('name', 'Empty name').isLength({ min: 1 }), 
    body('age', 'Invalid age').optional({ checkFalsy: true }).isISO8601(),
    
    You can also daisy chain different validators, and add messages that are displayed if the preceding validators are true. 
    body('name').isLength({ min: 1 }).trim().withMessage('Name empty.')
        .isAlpha().withMessage('Name must be alphabet letters.'),
    

    Note: You can also add inline sanitizers like trim(), as shown above. However sanitizers applied here ONLY apply to the validation step. If you want the final output sanitized, you need to use a separate sanitizer method, as shown below.

  • sanitizeBody(fields): Specifies a body field to sanitize. The sanitization operations are then daisy-chained to this method. For example, the escape() sanitization operation below removes HTML characters from the name variable that might be used in JavaScript cross-site scripting attacks.
    sanitizeBody('name').trim().escape(),
    sanitizeBody('date').toDate(),
  • validationResult(req): Runs the validation, making errors available in the form of a validation result object. This is invoked in a separate callback, as shown below:
    (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.
        }
    }
    We use the validation result's isEmpty() method to check if there were errors, and its array() method to get the set of error messages. See the Validation Result API for more information.

The validation and sanitization chains are middleware that should be passed to the Express route handler (we do this indirectly, via the controller). When the middleware runs, each validator/sanitizer is run in the order specified.

We'll cover some real examples when we implement the LocalLibrary forms below.

Form design

Many of the models in the library are related/dependent—for example, a Book requires an Author, and may also have one or more Genres. This raises the question of how we should handle the case where a user wishes to:

  • Create an object when its related objects do not yet exist (for example, a book where the author object hasn't been defined).
  • Delete an object that is still being used by another object (so for example, deleting a Genre that is still being used by a Book).

For this project we will simplify the implementation by stating that a form can only:

  • Create an object using objects that already exist (so users will have to create any required Author and Genre instances before attempting to create any Book objects).
  • Delete an object if it is not referenced by other objects (so for example, you won't be able to delete a Book until all associated BookInstance objects have been deleted).

Note: A more "robust" implementation might allow you to create the dependent objects when creating a new object, and delete any object at any time (for example, by deleting dependent objects, or by removing references to the deleted object from the database).

Routes

In order to implement our form handling code we will need two routes that have the same URL pattern. The first (GET) route is used to display a new empty form for creating the object. The second route (POST) is used for validating data entered by the user, and then saving the information and redirecting to the detail page (if the data is valid) or redisplaying the form with errors (if the data is invalid).

We have already created the routes for all our model's create pages in /routes/catalog.js (in a previous tutorial). For example, the genre routes are shown below:

// 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

The following subarticles will take us through the process of adding the required forms to our example application. You need to read and work through each one in turn, before moving on to the next one.

  1. Create Genre form — Defining our page to create Genre objects.
  2. Create Author form — Defining a page for creating Author objects.
  3. Create Book form — Defining a page/form to create Book objects.
  4. Create BookInstance form — Defining a page/form to create BookInstance objects.
  5. Delete Author form — Defining a page to delete Author objects.
  6. Update Book form — Defining  page to update Book objects.

Challenge yourself

Implement the delete pages for the Book, BookInstance, and Genre models, linking them from the associated detail pages in the same way as our Author delete page. The pages should follow the same design approach:

  • If there are references to the object from other objects, then these other objects should be displayed along with a note that this record can't be deleted until the listed objects have been deleted.
  • If there are no other references to the object then the view should prompt to delete it. If the user presses the Delete button, the record should then be deleted.

A few tips:

  • Deleting a Genre is just like deleting an Author as both objects are dependencies of Book (so in both cases you can delete the object only when the associated books are deleted).
  • Deleting a Book is also similar, but you need to check that there are no associated BookInstances.
  • Deleting a BookInstance is the easiest of all, because there are no dependent objects. In this case you can just find the associated record and delete it.

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

A few tips:

  • The Book update page we just implemented is the hardest! The same patterns can be used for the update pages for the other objects.
  • The Author date of death and date of birth fields, and the BookInstance due_date field are the wrong format to input into the date input field on the form (it requires data in form "YYYY-MM-DD"). The easiest way to get around this is to define a new virtual property for the dates that formats the dates appropriately, and then use this field in the associated view templates.
  • If you get stuck, there are examples of the update pages in the example here.

Summary

Express, node, and third party packages on NPM provide everything you need to add forms to your website. In this article you've learned how to create forms using Pug, validate and sanitize input using express-validator, and add, delete, and modify records in the database.

You should now understand how to add basic forms and form-handling code to your own node websites!

See also

En este modulo:

 

Etiquetas y colaboradores del documento

Colaboradores en esta página: PinkWhale
Última actualización por: PinkWhale,