Implementing Validation

1. Installing express-validator

PackageStyle
express-validatorChainable per-field
zodSchema-based, TS-first RECOMMENDED
joiObject schema (legacy hapi)
ajvJSON Schema (OpenAPI alignment)

2. Creating Validation Rules

Example: Per-field chains

import { body, query, param } from "express-validator";

const createUserRules = [
  body("email").isEmail().normalizeEmail(),
  body("password").isLength({ min: 12 }).matches(/[A-Z]/).matches(/\d/),
  body("age").optional().isInt({ min: 13, max: 120 }),
  query("ref").optional().isAlphanumeric()
];

3. Chaining Validation Methods

MethodPurpose
.exists()Field present
.notEmpty()Not empty string
.isEmail()Valid email
.isURL()Valid URL
.isLength({min,max})String length
.isInt({min,max})Integer range
.isUUID()UUID v1-5
.isISO8601()Date string
.isIn([...])Whitelist
.matches(regex)Regex pattern
.withMessage(msg)Override error msg

4. Using Custom Validators

Example: Async DB check

body("email").isEmail().custom(async (email) => {
  const exists = await db.users.findByEmail(email);
  if (exists) throw new Error("Email already in use");
})

5. Sanitizing Input

SanitizerEffect
.trim()Strip whitespace
.escape()HTML entity escape
.normalizeEmail()Lowercase, gmail dots removed
.toInt()Coerce to number
.toBoolean()"true"/"1" → true
.toDate()String → Date

6. Handling Validation Results

Example: Reusable handler

import { validationResult } from "express-validator";

export function handleValidation(req, res, next) {
  const errors = validationResult(req);
  if (errors.isEmpty()) return next();
  res.status(422).json({ errors: errors.array({ onlyFirstError: true }) });
}

app.post("/users", createUserRules, handleValidation, controller.create);

7. Returning Validation Errors

MethodOutput
errors.array()[{type, msg, path, value, location}]
errors.array({onlyFirstError:true})One error per field
errors.mapped()Object keyed by field name
errors.formatWith(fn)Custom shape

8. Creating Reusable Validators

Example: Shared rules

// validators/common.js
export const idParam = param("id").isUUID().withMessage("Invalid ID");
export const pagination = [
  query("page").optional().isInt({ min: 1 }).toInt(),
  query("limit").optional().isInt({ min: 1, max: 100 }).toInt()
];

// usage
router.get("/:id", idParam, handleValidation, controller.get);
router.get("/", pagination, handleValidation, controller.list);

9. Validating Nested Objects

Example: checkSchema

import { checkSchema } from "express-validator";

const orderSchema = checkSchema({
  "customer.email": { isEmail: true, errorMessage: "Invalid email" },
  "items": { isArray: { options: { min: 1 } } },
  "items.*.sku": { isString: true, notEmpty: true },
  "items.*.qty": { isInt: { options: { min: 1 } } }
});

10. Conditional Validation

Example: optional + if

body("phone").optional({ values: "falsy" }).isMobilePhone("any");
body("address").if(body("shipping").equals("true")).notEmpty();
ModifierBehavior
.optional()Skip if undefined
.optional({values:"falsy"})Skip if falsy ("", 0, null)
.if(cond)Apply only when condition true

11. Implementing Async Validation

Example: Multiple async checks

body("username")
  .isAlphanumeric()
  .isLength({ min: 3, max: 20 })
  .custom(async (username) => {
    if (RESERVED_NAMES.includes(username)) throw new Error("Reserved username");
    if (await db.users.findByUsername(username)) throw new Error("Username taken");
    return true;
  })
Note: Async validators run in parallel within a chain — keep them idempotent and side-effect-free.