Skip to content

Express Input Validation

Every piece of data a client sends should be treated as untrusted until validated. This lesson covers why validation matters and the basic manual patterns before introducing dedicated libraries in later lessons.

Why Validate Input at All?

Without validation, malformed or malicious input can crash your server, corrupt your database, or open serious security holes. Validation checks that incoming data has the expected shape, type, and constraints before your business logic ever touches it.

Validation should happen as early as possible in the request pipeline, ideally in dedicated middleware, before a controller or service assumes the data is well-formed.

app.post('/users', (req, res) => {
  const { email, age } = req.body;

  if (typeof email !== 'string' || !email.includes('@')) {
    return res.status(400).json({ error: 'Invalid email' });
  }
  if (typeof age !== 'number' || age < 0) {
    return res.status(400).json({ error: 'Invalid age' });
  }

  res.status(201).json({ email, age });
});

This manual approach works for a couple of fields, but becomes unwieldy quickly as the number of validated fields grows.

Where Validation Fits in the Pipeline

app.post(
  '/users',
  validateCreateUserBody, // validation middleware
  controller.createUser    // only runs if validation passed
);
  • Validation middleware runs before the route's main handler.
  • A failed validation should short-circuit with a 400 Bad Request and a clear error message.
  • Validation and sanitization are related but distinct, sanitization also transforms input (e.g. trimming whitespace).
  • For anything beyond a couple of fields, use a schema library like express-validator or Joi.

Validation Cheatsheet

Common validation checks you will apply to almost every input field.

Check Example Purpose
Required field if (!req.body.email) Reject missing values
Type check typeof value === 'string' Reject wrong data types
Format check /^[^@]+@[^@]+\.[^@]+$/ Basic email shape validation
Range check age >= 0 && age <= 120 Reject out-of-range numbers
Length check password.length >= 8 Enforce minimum password length
Enum check ['admin','user'].includes(role) Restrict to allowed values

Manual vs Library-Based Validation

Manual if checks are fine for one or two fields, but they scale poorly: error messages become inconsistent, edge cases get missed, and every endpoint reinvents the same checks. Validation libraries centralize rules into declarative schemas that are easier to read, test, and reuse.

  • express-validator: chainable validation functions built around express-validator's own middleware.
  • Joi: schema-based validation, define an object shape once and validate against it.
  • Zod: TypeScript-first schema validation, increasingly popular in modern Node APIs.

A Consistent Validation Error Format

Whichever approach you choose, return validation errors in a predictable shape so clients can map errors to specific form fields.

res.status(400).json({
  success: false,
  error: {
    message: 'Validation failed',
    fields: [
      { field: 'email', message: 'Invalid email format' },
      { field: 'age', message: 'Must be a positive number' },
    ],
  },
});

Common Mistakes

  • Trusting client-side validation alone, it can always be bypassed by calling the API directly.
  • Validating on the happy path only, forgetting edge cases like empty strings, null, or wrong types.
  • Returning inconsistent error shapes across different endpoints.
  • Mixing validation logic with business logic instead of isolating it in dedicated middleware.

Key Takeaways

  • Never trust client-supplied data, always validate it server-side, regardless of client-side checks.
  • Validation should run early, in dedicated middleware, before business logic executes.
  • Manual checks work for tiny cases, but schema libraries scale far better for real APIs.
  • Keep validation error responses in a consistent, field-mapped shape.

Pro Tip

Treat every field in req.body, req.query, and req.params as hostile input by default, validation is not about distrust of your users, it's about correctness in the face of any possible client behavior.