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 });
});