express-validator
A practical set of notes covering validation chains, sanitization, custom validators, error handling, field selection, wildcards, manual validation, and schema validation.
1. Getting Started
express-validator is used with Express to validate and sanitize request data.
Basic setup
const express = require('express');
const { query, validationResult } = require('express-validator');
const app = express();
app.get('/hello',
query('person').notEmpty(),
(req, res) => {
const result = validationResult(req);
if (!result.isEmpty()) {
return res.status(400).json({ errors: result.array() });
}
res.send(`Hello, ${req.query.person}`);
}
);
Important idea
Validation chains run against the request, but validators do not automatically send a response. You must inspect the result with validationResult(req) and decide how to respond.
2. Errors and matchedData()
After running validation, use validationResult(req) to retrieve validation errors.
const result = validationResult(req);
if (!result.isEmpty()) {
return res.status(400).json({ errors: result.array() });
}
A validation error can contain information such as the field, location, value, message, and path.
Sanitization
Sanitizers transform input before you use it. For example, escape() can be used to escape characters that could otherwise be involved in XSS.
query('search').escape()
matchedData(req)
matchedData(req) retrieves the data that matched the validation/sanitization chains.
const data = matchedData(req);
This is useful when you want to work with the validated and sanitized request data rather than using the entire raw request object.
3. Validation Chains
A validation chain is built from a field location followed by validators, sanitizers, and modifiers.
Common field locations include:
body()param()query()
Example:
body('email')
.optional()
.trim()
.isEmail();
Validators
Validators check whether the value satisfies a condition.
Examples from the notes:
.isEmail()
.isLength({ min: 6 })
.notEmpty()
Sanitizers
Sanitizers transform the value.
Examples:
.trim()
.escape()
Modifiers
Modifiers change how a validation chain behaves.
For example:
.optional()
4. Types, Order, and Reusing Chains
Standard validators cast values to strings
Standard validators operate on values as strings. Keep this behavior in mind when validating request data.
Order matters
The order of validators and sanitizers can change the result.
For example:
trim().notEmpty()
is different from:
notEmpty().trim()
If whitespace needs to be removed before checking whether a value is empty, the order of the chain matters.
Validation chains are mutable
Chains are mutable. When you build reusable validation chains, be careful not to unintentionally modify a chain that is reused elsewhere.
Factory functions can be used when you need a fresh chain each time.
5. Field Selection and Paths
express-validator supports selecting fields using paths.
Examples include nested fields and array indexes.
For nested data, the path describes where the field exists inside the request data.
Arrays and nested fields
Wildcard selection can be used when the same validation needs to be applied to multiple array elements.
For example:
body('siblings.*.name').notEmpty()
This selects the name field from every object inside siblings.
A path such as:
siblings.name
does not select the name field inside every sibling object. Use the wildcard when the field exists inside array elements.
Special characters in field names
When a field name itself contains special characters such as a dot, the field path needs to represent the literal field name correctly.
The notes use bracket notation such as:
body('["a.b"]')
for a literal field name containing a dot.
6. Wildcards and Globstars
Wildcards are useful when working with dynamic nested data.
* — wildcard
* matches one path segment.
Example:
body('siblings.*.name').notEmpty()
** — globstar
** can match any depth in a nested structure.
The distinction is:
-
*→ one segment -
**→ any depth
This becomes useful when the structure can contain nested levels that are not fixed in advance.
7. Custom Validators and Sanitizers
When built-in validators are not enough, you can create custom validation logic with .custom().
Custom validator
body('email').custom(value => {
// custom validation logic
return true;
});
A custom validator can also be asynchronous.
body('email').custom(async value => {
// async validation logic
});
If a custom validator throws an error or rejects, the validation fails.
Custom sanitizer
Custom sanitizers transform the value.
An important rule from the notes: a custom sanitizer must return the transformed value.
Example:
body('name').customSanitizer(value => {
return value.trim();
});
8. Error Messages
Validation errors can have custom messages using .withMessage().
body('email')
.isEmail()
.withMessage('Bad e-mail');
Errors can also come from thrown errors inside custom validators.
Message precedence
The notes cover field-level fallback and message precedence. The important practical point is to define the message at the validation step where you want the specific error to be associated.
Example:
body('password')
.isLength({ min: 6 })
.withMessage('Password must be at least 6 characters');
9. ExpressValidator Class
The ExpressValidator class can be used to create an instance containing custom validators and sanitizers.
This is useful when you want to organize reusable validation logic.
The notes cover adding custom validators and custom sanitizers through the class and then using the resulting validation helpers in the application.
10. Manually Running Validation
Validation chains do not have to be used only as Express route middleware.
They can also be run manually with .run(req).
await validationChain.run(req);
This uses the ContextRunner behavior to execute the validation chain against the request.
Manual running is useful when validation needs to be triggered from application logic rather than directly placed in the middleware list.
11. Schema Validation with checkSchema()
For larger validation rules, checkSchema() can define validation using an object schema.
Example structure:
const { checkSchema } = require('express-validator');
const userSchema = {
email: {
trim: true,
isEmail: true,
},
password: {
isLength: {
options: { min: 6 },
},
},
};
app.post('/signup',
checkSchema(userSchema),
(req, res) => {
const result = validationResult(req);
if (!result.isEmpty()) {
return res.status(400).json({ errors: result.array() });
}
res.json({ message: 'Valid' });
}
);
Schema wildcard keys
When schema keys contain wildcards, quote the keys.
This matters for paths such as:
const schema = {
'siblings.*.name': {
notEmpty: true,
},
};
Complete Signup Example
A simple end-to-end example combines field selection, sanitization, validation, error handling, and matchedData().
const express = require('express');
const {
body,
validationResult,
matchedData,
} = require('express-validator');
const app = express();
app.use(express.json());
app.post('/signup',
body('email')
.trim()
.isEmail()
.withMessage('Bad e-mail'),
body('password')
.isLength({ min: 6 }),
(req, res) => {
const result = validationResult(req);
if (!result.isEmpty()) {
return res.status(400).json({
errors: result.array(),
});
}
const { email, password } = matchedData(req);
// ... create the user
res.json({
email,
password,
});
}
);
The basic flow is:
Request
↓
Select field location
↓
Run validators / sanitizers / modifiers
↓
validationResult(req)
↓
If errors → return error response
↓
matchedData(req)
↓
Use validated/sanitized data
Express-Validator Cheat Sheet
The basic recipe
- Choose the location:
body(),query(), orparam(). - Add validation chains.
- Run the request through the validators.
- Read errors with
validationResult(req). - Get validated/sanitized data with
matchedData(req).
Validators
Validators check values.
Examples:
.isEmail()
.notEmpty()
.isLength({ min: 6 })
Sanitizers
Sanitizers transform values.
Examples:
.trim()
.escape()
Modifiers
Modifiers change chain behavior.
Example:
.optional()
Field locations
body('field')
query('field')
param('field')
Wildcards
* → one path segment
** → any depth
ContextRunner
Run a validation chain manually with:
await chain.run(req);
ExpressValidator
Use the class when organizing custom validators and sanitizers for reuse.
Important Gotchas
1. Validators do not send responses
You still need to check:
validationResult(req)
and decide what response to return.
2. Order matters
For example:
trim().notEmpty()
and:
notEmpty().trim()
can behave differently because the operations happen in the order they appear in the chain.
3. Chains are mutable
Be careful when reusing a validation chain. Factory functions can help create a fresh chain.
4. Custom sanitizers must return
A custom sanitizer needs to return the transformed value.
5. Use wildcards for array elements
Instead of:
siblings.name
use:
siblings.*.name
when siblings contains multiple objects.
6. Literal dotted keys need special handling
If the actual field name contains a dot, represent the literal key correctly, such as:
["a.b"]
7. Quote wildcard keys in schemas
For checkSchema(), use quoted keys for wildcard paths:
'siblings.*.name'
8. Prefer .if() for conditional validation
The notes recommend using .if() rather than manually running conditional validation logic.
Final Mental Model
Think about express-validator as a pipeline:
REQUEST
│
├── body / query / params
│
▼
FIELD SELECTION
│
▼
VALIDATION CHAIN
│
├── validators → check
├── sanitizers → transform
└── modifiers → change behavior
│
▼
validationResult(req)
│
├── errors → return response
│
└── valid
│
▼
matchedData(req)
│
▼
Use validated/sanitized data
These notes cover the core concepts from express-validator v7.3.0: getting started, validation results, sanitization, matched data, validation chains, field selection, wildcards, custom validators and sanitizers, error messages, the ExpressValidator class, manual validation, and schema validation.
Top comments (0)