Express middleware for comprehensive request validation and sanitization.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Extract validation results, handle errors, and retrieve validated data from requests after running validation middleware.
Extract validation results from requests to check for errors and handle validation outcomes.
/**
* Extract validation result from request
* @param req - Express request object
* @returns Result instance containing validation outcomes
*/
function validationResult(req: Request): Result;
interface Result {
/** Check if validation passed (no errors) */
isEmpty(): boolean;
/** Get validation errors as array */
array(options?: { onlyFirstError?: boolean }): ValidationError[];
/** Get validation errors mapped by field path */
mapped(): Record<string, ValidationError>;
/** Format errors using custom formatter */
formatWith<T>(formatter: ErrorFormatter<T>): T;
/** Throw validation errors if any exist */
throw(): never;
}
type ErrorFormatter<T> = (error: ValidationError) => T;Create a validationResult-like function with default options for consistent error formatting across your application.
/**
* Create a validationResult function with default options
* @param options - Default options for all Result instances
* @returns ResultFactory function that works like validationResult
*/
function withDefaults<T = any>(
options?: Partial<ResultFactoryBuilderOptions<T>>
): ResultFactory<T>;
interface ResultFactoryBuilderOptions<T = any> {
/** Default error formatter for all Result instances */
formatter: ErrorFormatter<T>;
}
type ResultFactory<T> = (req: Request) => Result<T>;Usage Examples:
import { validationResult } from "express-validator";
// Create custom validation result with default formatter
const customValidationResult = validationResult.withDefaults({
formatter: (error) => ({
field: error.path,
message: error.msg,
code: `ERR_${error.path.toUpperCase()}`
})
});
// Use in middleware
app.post('/api/user', validationChains, (req, res) => {
const errors = customValidationResult(req);
if (!errors.isEmpty()) {
// Errors are automatically formatted with default formatter
return res.status(400).json({
success: false,
errors: errors.array() // Uses default formatter
});
}
});
// Create legacy format compatibility
const legacyValidationResult = validationResult.withDefaults({
formatter: (error) => ({
param: error.path,
msg: error.msg,
value: error.value
})
});
// API versioning with different error formats
const v1ValidationResult = validationResult.withDefaults({
formatter: (error) => ({ field: error.path, error: error.msg })
});
const v2ValidationResult = validationResult.withDefaults({
formatter: (error) => ({
path: error.path,
message: error.msg,
location: error.location,
severity: 'error'
})
});Usage Examples:
import { body, validationResult } from "express-validator";
app.post('/user', [
body('email').isEmail(),
body('name').trim().isLength({ min: 2 }),
body('age').isInt({ min: 18 })
], (req, res) => {
const errors = validationResult(req);
// Check if validation passed
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
// Proceed with valid data
res.json({ success: true, message: 'User created' });
});
// Get only first error per field
app.post('/quick-validation', validationChains, (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array({ onlyFirstError: true })
});
}
// Handle success
});Understanding the structure of validation errors returned by the Result object.
interface ValidationError {
type: 'field' | 'alternative' | 'alternative_grouped' | 'unknown_fields';
location: Location;
path: string;
value: any;
msg: string;
}
interface AlternativeValidationError {
type: 'alternative';
msg: string;
nestedErrors: ValidationError[];
}
interface UnknownFieldsError {
type: 'unknown_fields';
msg: string;
fields: UnknownFieldInstance[];
}Usage Examples:
// Handle different error types
app.post('/api/data', validationChains, (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const errorList = errors.array();
// Separate different error types
const fieldErrors = errorList.filter(err => err.type === 'field');
const alternativeErrors = errorList.filter(err => err.type === 'alternative');
return res.status(400).json({
fieldErrors,
alternativeErrors,
count: errorList.length
});
}
});Get errors organized by field path for easier frontend handling.
// Get errors mapped by field
app.post('/form', validationChains, (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const mappedErrors = errors.mapped();
// mappedErrors structure:
// {
// 'email': { type: 'field', location: 'body', path: 'email', value: 'invalid', msg: 'Invalid email' },
// 'name': { type: 'field', location: 'body', path: 'name', value: '', msg: 'Name is required' }
// }
return res.status(400).json({
success: false,
fieldErrors: mappedErrors
});
}
});Format validation errors using custom formatters for consistent error responses.
// Custom error formatter
const customFormatter = (error: ValidationError) => ({
field: error.path,
message: error.msg,
rejectedValue: error.value
});
app.post('/api/custom-errors', validationChains, (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const formattedErrors = errors.formatWith(customFormatter);
return res.status(400).json({
success: false,
validation_errors: formattedErrors
});
}
});
// Legacy format compatibility
const legacyFormatter = (error: ValidationError) => ({
param: error.path,
msg: error.msg,
value: error.value
});Automatically throw validation errors for centralized error handling.
// Middleware to auto-throw validation errors
const handleValidationErrors = (req, res, next) => {
const errors = validationResult(req);
try {
errors.throw(); // Throws if errors exist
next(); // Continue if no errors
} catch (error) {
// Handle thrown validation errors
res.status(400).json({
error: 'Validation failed',
details: errors.array()
});
}
};
app.post('/auto-throw', validationChains, handleValidationErrors, (req, res) => {
// This handler only runs if validation passed
res.json({ success: true });
});Extract only validated and sanitized data from requests.
/**
* Extract validated data from request
* @param req - Express request object
* @param options - Options for data extraction
* @returns Object containing only validated fields
*/
function matchedData(req: Request, options?: MatchedDataOptions): any;
interface MatchedDataOptions {
/** Include only successfully validated data (default: true) */
onlyValidData?: boolean;
/** Include optional fields that weren't provided (default: false) */
includeOptionals?: boolean;
/** Specific locations to extract data from */
locations?: Location[];
}Usage Examples:
import { body, matchedData, validationResult } from "express-validator";
app.post('/user', [
body('email').isEmail().normalizeEmail(),
body('name').trim().isLength({ min: 2 }),
body('age').isInt({ min: 18 }).toInt(),
body('bio').optional().trim()
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Get only validated and sanitized data
const userData = matchedData(req);
// userData contains only: email, name, age, bio (if provided)
// All data is sanitized (email normalized, name trimmed, age converted to int)
console.log(userData);
// { email: 'user@example.com', name: 'John Doe', age: 25 }
});
// Extract from specific locations only
app.get('/search', [
query('q').trim().isLength({ min: 1 }),
query('limit').optional().isInt({ min: 1, max: 100 }).toInt(),
query('offset').optional().isInt({ min: 0 }).toInt()
], (req, res) => {
const queryData = matchedData(req, { locations: ['query'] });
// Only includes validated query parameters
performSearch(queryData);
});
// Include optional fields
app.put('/profile', [
body('name').trim().notEmpty(),
body('bio').optional().trim(),
body('website').optional().isURL()
], (req, res) => {
const profileData = matchedData(req, {
includeOptionals: true
});
// Includes optional fields even if not provided (as undefined)
updateProfile(profileData);
});Matched data automatically includes all sanitization transformations applied during validation.
app.post('/product', [
body('name').trim().escape(), // Trimmed and HTML-escaped
body('price').toFloat(), // Converted to number
body('tags').toArray(), // Converted to array
body('description').trim().stripLow() // Trimmed and low chars removed
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const productData = matchedData(req);
// All sanitization is already applied:
// - name: trimmed and HTML-escaped
// - price: converted to float
// - tags: converted to array
// - description: trimmed with low ASCII chars removed
saveProduct(productData);
});Common patterns for handling validation results and extracting data.
// Utility function for validation handling
const validateAndExtract = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array(),
message: 'Validation failed'
});
}
// Add validated data to request for next middleware
req.validatedData = matchedData(req);
next();
};
app.post('/api/order', [
body('items').isArray({ min: 1 }),
body('shippingAddress.street').trim().notEmpty(),
body('shippingAddress.city').trim().notEmpty(),
body('paymentMethod').isIn(['card', 'paypal'])
], validateAndExtract, (req, res) => {
// req.validatedData contains clean, validated data
const { items, shippingAddress, paymentMethod } = req.validatedData;
processOrder({ items, shippingAddress, paymentMethod });
res.json({ success: true });
});
// Handle partial updates with matched data
app.patch('/user/:id', [
param('id').isUUID(),
body('name').optional().trim().isLength({ min: 2 }),
body('email').optional().isEmail().normalizeEmail(),
body('age').optional().isInt({ min: 18 }).toInt()
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { id } = matchedData(req, { locations: ['params'] });
const updates = matchedData(req, { locations: ['body'] });
updateUser(id, updates);
res.json({ success: true });
});