The author selected OWASP Foundation to receive a donation as part of the Write for DOnations program.
Modern web developers always deal with inputs, whether they be web forms, file uploads, or any other input on their websites. Therefore, it becomes essential for them to ensure that the inputs they receive from users are legit and harmless.
With advancements in web development, attackers have developed more and have found ways of exploiting websites from different aspects, such as form inputs. Attacks like Cross-Site Scripting (XSS) and SQL Injection have become more common and harmful than ever before. Therefore, any developer needs to ensure that the data they receive from a user is non-malicious or sanitized to ensure the integrity of their web application and server.
In this tutorial, you will learn how to use the Express-Validator package of JavaScript to validate and sanitize user inputs in web forms to ensure data consistency and server security.
To understand this tutorial properly, you should have the following setup:
Install Node Js with version 14.x or higher on your system. You can use this DO tutorial to install the latest Node Js version on your system.
You should know how to code in JavaScript.
You should have Express JS version 4.x or higher installed on your system. You can use this to set up an Express server.
After meeting these requirements, you can continue with the tutorial.
Input validation and Sanitization is a key task for a developer for various reasons mentioned below:
Prevention from Injection Attacks: Consider a scenario where your application is vulnerable to SQL Injection attacks. This vulnerability arises due to the poor handling of SQL code during user authentication through an authentication form. An attacker can exploit this by passing some malicious SQL code instead of user credentials and gaining access to the server, which is game over for the application.
Data Integrity and Consistency: When user input is validated, it creates consistency in data being stored in servers, thus making it complicated to work with the data. For example, if a user can send text data in input for age, it creates inconsistency in the data stored in the server.
Data Compatibility: The data type must be consistent when data is used at various endpoints in a big organization. For instance, if users can enter garbage data instead of a proper email in their email credentials, it could cause complications when the organization needs to contact the user.
Enhanced User Experience: When inputs are validated, a developer can create logic to send appropriate and immediate feedback to users, allowing them to correct the invalid input they provided. This enhances the overall User Experience.
There are many such reasons to ensure that, as a developer, you handle the inputs in your web forms and websites efficiently and securely. In the following sections, you will learn how to create validation logic for your form inputs using the Express-Validator
package in an Express Js app.
Validator – A validator is a function that takes input and performs certain checks based on some criteria.
Sanitizer – A sanitizer is a function used to modify or cleanse input data to ensure that it is secure and adheres to required formats.
Validation Chain – A validation chain in Express-Validator is a sequence of validators or sanitizers applied on an input. For example, assume that you have a form where you want users to enter their email and, to keep your data in the database consistent, you want no whitespaces to be allowed in the input (at left or right ends). You can use a validation chain like .isEmail()
to achieve this.trim()
[You will learn about these in a later section]. This will first check whether the input is an email; if it is email data, then the trim()
sanitizer will remove whitespaces from both ends of the input.
Now, you will learn to implement input validation and sanitization techniques in the following sections of this tutorial. Firstly, you will set up an npm project and then install Express Js, Express-Validator, and other dependencies required to follow the tutorial.
To create an Express server, first, you need to create an npm project. Firstly, open a terminal and type the following commands:
cd <project directory>
npm init
Then, you will be asked for inputs asking information about the application; you can either enter the specific details or continue pressing ‘Enter.’ Now, create an index.js file in the project folder; this will be the entry point for the server. Lastly, you must install the Express Js package to create an Express server. This can be done with the following command.
npm install express
You now have the Express Js installed in your project. The next task is to install Express-Validator,
which can be done with the following command.
npm install Express-Validator
We have satisfied basic dependencies till now; however, you can install nodemon
in your project, which is useful to keep a Node Js app running even if some error occurs. Type the following command to install it.
npm install nodemon
We now have installed all requirements and can start working on the server and input validation in forms. This project will be the base structure used in all the later sections, where each section explains a different topic.
The next section explains how Express-Validator
works behind the scenes and how you can use it in forms to perform input validations on various fields.
Express-Validator
is a combination of middleware provided by the Express JS module and the Validator.js module, which provides validators and sanitizers for string data types. Express-Validator
provides the functionality to validate input data by using a validation chain.
In this section, you will learn to use the validators in Express-Validator
using the Node Js project set up in the previous section. For this, you will create a route in Express Server that allows users to sign up for a web application with their credentials (name, email, password). Then, you will create middleware to validate the inputs every time a user signs up for your server.
Open the index.js file and type the following code into it.
- const express = require("express");
- const app = express();
- const { body, validationResult } = require("express-validator");
-
- // Express.js middleware to use JSON objects
- app.use(express.json());
-
- app.post(
- "/signup",
- // using validation to verify valid inputs (MIDDLEWARE)
- [
- [
- body("name").notEmpty(),
- body("email").isEmail(),
- body("password").notEmpty(),
- ],
- ],
- async (req, res) => {
- const errors = validationResult(req);
-
- if (!errors.isEmpty()) {
- return res.status(400).json({ errors: errors.array() });
- }
-
- res.status(200).json({success:'Successful Sign Up!'})
- }
- );
-
- // Server Listening at port 3000
- const port = 3000;
- app.listen(port, () => {
- console.log(`Listening at http://localhost:${port}`);
- });
Here, we import the Express Js and then create an Express app. Then, we use JavaScript destructing to import the body and validationResult functions from the Express-Validator
.
Info
The body()
method is used to create validation chains for validating and selecting input data from request payloads( req) from users, such as data sent by a POST request.
The validationResult()
method stores the result of a validation chain of an HTTP request as a JavaScript object. If this object is empty, it implies that the payload has passed all validation tests; if not, it stores information about the payload and the criteria it did not satisfy.
Then, we use the JSON middleware of Express J to work with JSON objects.
Finally, we create an Express route using the HTTP - POST (You can work with any other type of request, such as GET, PUT, etc.) request. The route we have created here represents a route to a signup form on a web server. The form takes input using client-side HTML, and the server handles it by accessing the input fields of the web form using the body of HTML DOM.
The body()
method of the Express-Validator
fetches the values of HTML components having the name attribute same as the argument of the body()
method; i.e., body(“password”)
will fetch the value of HTML input component having the name attribute as password.
Then, on these values, you can use the validators provided by Express-Validator
. In this example, we have used two validators, isEmail()
and notEmpty()
. These validators determine whether the input is email and not empty, respectively. If the input does not match the criteria of the validator(s), the Express-Validator will throw an error as an object in the form of the validationResult()
. However, if the input matches the criteria of the applied validators, the validationResult()
object will be empty. This is then used inside the function definition of the ‘/signup’ route to create a simple check using if
statement. Suppose the validationResult()
array is not empty. In that case, an error with response code 400 is sent to the client. Otherwise, in this example, a message of successful sign-up is sent to the client (This is just for exemplary purposes; you can perform any task, such as storing the input data in a database, using the input credentials to authenticate a user, etc.). Then, we host the server on localhost at port 3000.
These basic validators were used in this example; however, you can use any other validator offered by the Express-Validator
package. In the following table, you can find some commonly used validators, which can be used in the same manner as isEmail()
and notEmpty()
.
Validator | Usage |
---|---|
isDate | Checks if the Input is a Date object. |
isEmpty | Checks if the Input is an Empty string. |
isHash(algorithm) | Checks if the Input is a hash. The argument takes the hashing algorithm such as MD5, SHA, etc. |
isJWT | Checks if the Input is a JWT(JavaScript Web Token). |
isURL | Checks if the Input is a URL. |
isBoolean | Checks if the Input is Boolean(True or False). |
isNumeric | Checks if the Input is of numeric data type. |
isAlphanumeric | Checks if the Input is Alphanumeric. |
isInt | Checks if the Input is an Integer type. |
isDecimal | Checks if the Input is in Base Decimal. |
isFloat | Checks if the Input is a Floating Point Number. |
Now, in the following section, you will learn how to validate file inputs to make file uploads through your form (such as a photograph or a signature) secure and controlled.
In the previous section, you learned how to use the Express-Validator
to validate inputs generally passed to web forms. In this section, you’ll learn how to handle file inputs and validate them before uploading them to a server to avoid uploading malicious files on your server. In addition to this security benefit, you can put constraints on the file input size, ensuring no large files fill up your server.
We will use the multer
package of Node Js to handle file uploads using forms. You can use this extensive DO tutorial to understand how multer
works, Uploading files with multer in Node.js and Express. Now, Express-Validator
does not provide specific functionality for file input handling; however, you can combine the multer
library as an extension to Express-Validator
for handling file input validation for your web forms. Before using multer
, you must install the package in your Node Js project, which can be done by following the command.
cd <project folder>
npm install multer
Now, you will see how to integrate multer
with your Express server using Express-Validator
. In the following code, you will implement multer
and Express-Validator
and put some constraints on uploaded files.
- const express = require("express");
- const app = express();
- const { body, validationResult } = require("express-validator");
- const multer = require("multer");
-
- // Express.js middleware to use JSON objects
- app.use(express.json());
-
- // CREATING MIDDLEWARE USING MULTER TO HANDLE UPLOADS
-
- // creating a storage object for multer
- const storage = multer.diskStorage({
- // providing the destinations for files to be stored in server
- destination: "./uploads/",
- });
-
- // defining file storage to local disk and putting constraints on files being uploaded
- const upload = multer({
- storage: storage,
- limits: { fileSize: 1*1024*1024 }, // file size in bytes
-
- // you can add other constraints according to your requirement such as file type, etc.
- });
-
- app.post(
- "/fileUpload",
-
- // input validation for files to ensure only a single file is uploaded in one request
- upload.single("fileUpload"),
-
- // using validation to verify valid inputs
- [
- body("fileDescription").notEmpty(),
- // you can add as many validators as you require
- ],
- async (req, res) => {
- const errors = validationResult(req);
-
- if (!errors.isEmpty()) {
- return res.status(400).json({ errors: errors.array() });
- }
- const file = req.file;
- // perform any other tasks on the file or other form inputs
-
- res.status(200).json({ success: "Successful Sign Up!" });
- }
- );
-
- // Server Listening at port 3000
- const port = 3000;
- app.listen(port, () => {
- console.log(`Listening at http://localhost:${port}`);
- });
Here, we have defined the necessary imports and the Express JSON middleware to work with JSON objects over a network. Then, we define the storage-server for multer
to work. The example is explained in detail below:
First, we define a storage category. Multer
provides both disk storage and memory storage(buffer). In this example, we have used disk storage to store uploaded files on a server’s local disk instead of wasting memory.
When using disk storage, it is necessary to provide the directory location where uploaded files will be stored. This can be done using the destination keyword, which takes the path as a value. Here, we have used an uploads folder in the project directory itself.
After this, we create an upload object which handles the upload of files. This calls the multer
import and takes a JavaScript object as input. This JavaScript object contains various constraints/validations to be satisfied before storing the input file on disk. Storage is a necessary argument that will take the storage object as input, defined before this.
We have added another validation using the limits option to ensure no file is greater than 1 MB. Note that it counts size in bytes.
Info: You can make other validations/checks in the limits object according to your requirement.
Then, we create an Express route to handle file upload and add a middleware upload.single()
, ensuring that only one file is uploaded in one request. It takes the input field named fileUpload from HTML DOM as the required argument.
After this, we add Express-Validator middleware, as explained in the previous section.
Lastly, we create the logic to handle the route and check for any errors in input validation.
You can access the uploaded file(s) from your route logic from the req.file object.
Here, you learned how to handle file input validation using Multer
and integrate it with the Express-Validator
validation middleware. In addition, you can add many other advanced or custom validation methods in Multer
and Express-Validator. In the next section, you will learn how to sanitize input values, which is necessary to prevent attacks like SQL Injection.
So far, you have learned how to perform input validations on data and file input. Now, you will extend that knowledge by learning how to sanitize (validated) inputs. Express-Validator
provides many sanitizers from the Validator.js library.
For explaining sanitizers, we will use a similar approach as the ‘Basic form validation techniques in Express-Validator
and add sanitizers in the route.
- const express = require("express");
- const app = express();
- const { body, validationResult } = require("express-validator");
-
- // Express.js middleware to use JSON objects
- app.use(express.json());
-
-
- app.post(
- "/sanitizedInput",
- // sanitizing inputs
- [
- [
- body("name").notEmpty().trim(),
- body("email").isEmail().trim(),
- body("dob").toDate(),
- ],
- ],
- async (req, res) => {
- const errors = validationResult(req);
-
- if (!errors.isEmpty()) {
- return res.status(400).json({ errors: errors.array() });
- }
- // remaining logic for your route
- res.status(200).json({success:'Successful Sign Up!'})
- }
- );
-
- // Server Listening at port 3000
- const port = 3000;
- app.listen(port, () => {
- console.log(`Listening at http://localhost:${port}`);
- });
You create another Express server by importing necessary packages and using the Express JSON middleware to handle JSON objects. Then, you will create a route to handle sanitized input (/sanitizedInput
) and write the route logic.
Again, you need to add Express-Validator
middleware. Here, we use three input fields: name
, email
, and dob
. First, we check if the name is not empty using notEmpty()
and then apply the trim()
sanitizer on it, which removes whitespaces from both ends of the input value. Similarly, for the email input field, we first check whether it is a valid email using isEmail()
and then apply trim()
sanitizer to remove whitespaces from both ends.
In the dob
input field, we use the toDate()
sanitizer (without any validator) to convert the input string into a date object. If the string is not an actual date, toDate()
will return a null value.
After that, you will perform the check to ensure that there are no errors stored in the validationResult()
object and can continue with the rest of the logic for your route.
Note:
Using multiple validators and sanitizers on a single input, as done with body("name").notEmpty().trim()
, creates a chain of validators/sanitizers. Such a chain or sequential use of validators/sanitizers creates a validation chain.
There are plenty more sanitizers available with Express-Validator
, such as blacklist()
, whitelist()
, etc. You can use them according to your requirements.
In this tutorial, you learned how to effectively handle form inputs on your server. You built servers using the Express-Validator
library that used input validation and sanitization techniques. You can build upon this knowledge and extend your input validation further by using different validators and sanitizers provided by the same library.
Express-Validator
and Multer
both allow you to create custom validators, which you can try as an exercise to increase your productivity in complex projects.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!