Episode 3 — NodeJS MongoDB Backend Architecture / 3.10 — Input Validation

3.10 — Quick Revision: Input Validation

A concise cheat sheet covering validation concepts, express-validator, Zod, and error handling patterns.


< Interview Questions | README


Core Concepts

ConceptDefinition
ValidationChecking data meets rules — accept or reject
SanitizationTransforming data to be safe/clean
Defense in depthValidate at every boundary (client, middleware, schema, DB)
Mass assignmentVulnerability from passing raw req.body to database
Trust boundaryAny point where data enters from an external source

express-validator Cheat Sheet

npm install express-validator
const { body, param, query, validationResult } = require('express-validator');

// Field targets
body('field')    // req.body
param('field')   // req.params
query('field')   // req.query

// Common validators
.notEmpty()                    // Not empty
.isEmail()                     // Valid email
.isLength({ min: 2, max: 50}) // String length
.isInt({ min: 0, max: 100 })  // Integer range
.isFloat({ min: 0 })          // Float
.isURL()                       // Valid URL
.isMongoId()                   // MongoDB ObjectId
.isStrongPassword()            // Complex password
.isIn(['a', 'b', 'c'])        // Whitelist
.matches(/regex/)              // Regex match
.isBoolean()                   // Boolean

// Sanitizers
.trim()                        // Remove whitespace
.escape()                      // HTML escape
.normalizeEmail()              // Lowercase + normalize
.toInt()                       // Convert to integer
.toFloat()                     // Convert to float
.toLowerCase()                 // Lowercase

// Custom validator
.custom(async (value, { req }) => {
  const exists = await Model.findOne({ field: value });
  if (exists) throw new Error('Already exists');
  return true;
})

// Error messages
.withMessage('Custom error message')

// Extract errors
const errors = validationResult(req);
errors.isEmpty()               // boolean
errors.array()                 // [{ msg, path, value, location }]
errors.mapped()                // { field: { msg, ... } }

Zod Cheat Sheet

npm install zod
const { z } = require('zod');

// Primitives
z.string()    z.number()    z.boolean()    z.date()

// String refinements
.email()  .url()  .uuid()  .min(n)  .max(n)  .length(n)
.regex(/pattern/)  .trim()  .toLowerCase()  .startsWith()

// Number refinements
.int()  .positive()  .nonnegative()  .min(n)  .max(n)  .multipleOf(n)

// Object
z.object({ field: z.string() })

// Array
z.array(z.string()).min(1).max(10)

// Enum
z.enum(["a", "b", "c"])

// Optional / Nullable / Default
.optional()     // T | undefined
.nullable()     // T | null
.default(val)   // Uses val if undefined

// Parsing
schema.parse(data)              // Returns data or throws ZodError
schema.safeParse(data)          // Returns { success, data/error }

// Type inference
type MyType = z.infer<typeof mySchema>;

// Transform
.transform(val => val * 100)

// Preprocess (before validation)
z.preprocess(val => Number(val), z.number())

// Custom validation
.refine(val => val > 0, { message: "Must be positive" })
.refine(async val => !(await exists(val)), { message: "Taken" })
// Use parseAsync() / safeParseAsync() for async refine

Error Response Format

// Standard success
{ "success": true, "data": { ... } }

// Standard error
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Input validation failed",
    "details": {
      "email": ["Must be a valid email"],
      "password": ["Too short", "Needs uppercase"]
    }
  }
}
HTTP StatusUse For
400Malformed request / validation errors
401Authentication required
403Forbidden (authorized but not allowed)
404Resource not found
409Conflict (duplicate)
422Semantically invalid (alternative to 400)
429Rate limit exceeded
500Server error

Reusable Middleware Pattern

// express-validator
const validate = (validations) => async (req, res, next) => {
  await Promise.all(validations.map(v => v.run(req)));
  const errors = validationResult(req);
  if (errors.isEmpty()) return next();
  res.status(400).json({ success: false, error: { code: 'VALIDATION_ERROR', details: errors.array() } });
};

// Zod
const zodValidate = (schema) => (req, res, next) => {
  const result = schema.safeParse(req.body);
  if (result.success) { req.validatedBody = result.data; return next(); }
  res.status(400).json({ success: false, error: { code: 'VALIDATION_ERROR', details: result.error.flatten().fieldErrors } });
};

express-validator vs Zod

Featureexpress-validatorZod
TypeScript typesManualz.infer
FrameworkExpress onlyAny
Async validation.custom(async).refine(async) + parseAsync
SanitizationBuilt-in.transform()
Size~50KB~13KB
Best forExpress JS projectsTypeScript, shared schemas

Security Reminders

  • Always validate server-side — client validation is UX only
  • Whitelist fields — never pass raw req.body to database
  • Type-check inputs — strings, numbers, booleans (prevents NoSQL injection)
  • Limit payload sizeexpress.json({ limit: '1mb' })
  • Escape HTML — prevents stored XSS
  • Generic auth errors — "Invalid credentials" not "Wrong password"
  • Never log passwords/tokens in error details