Skip to content

Request Validation in Node.js APIs

Trusting incoming request data without validation is one of the most common sources of bugs and security issues in APIs. This lesson covers validating request bodies, params, and queries before they reach your business logic.

Why Validate Requests?

Client input can be missing, the wrong type, out of range, or maliciously crafted. Validating requests at the edge of your API, before any business logic or database call runs, ensures downstream code can trust the shape of the data it receives, and lets you return a clear 400 Bad Request instead of a confusing crash.

For small APIs, manual checks are reasonable. As the number of fields and rules grows, schema-based validation libraries (like zod or joi) express validation rules declaratively and produce consistent, detailed error messages automatically.

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

  if (typeof email !== 'string' || !email.includes('@')) {
    return res.status(400).json({ error: 'A valid email is required' });
  }

  if (typeof age !== 'number' || age < 0) {
    return res.status(400).json({ error: 'Age must be a non-negative number' });
  }

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

Each validation check returns early with a 400 status, business logic below this point can safely assume email and age are valid.

Schema-Based Validation With Zod

import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email(),
  age: z.number().nonnegative(),
});

const result = userSchema.safeParse(req.body);
if (!result.success) {
  return res.status(400).json({ errors: result.error.issues });
}
  • Schema libraries let you declare validation rules once and reuse them across routes.
  • safeParse() (Zod) returns a result object instead of throwing, ideal for handling in route code.
  • Validation errors should map to 400 Bad Request, not 500 Internal Server Error.
  • Validate req.body, req.params, and req.query separately when each has different rules.

Request Validation Cheatsheet

Where and how to validate different parts of a request.

Source Example Rule Failure Status
req.body Required fields, types, string formats 400
req.params ID must be a valid number/UUID 400
req.query page/limit must be positive integers 400
req.headers Required API key or content type 400/401
Business rule failure e.g. "email already registered" 409

Turning Validation Into Middleware

Rather than repeating validation logic inside every route handler, a small reusable middleware factory can validate a request against a given schema and short-circuit with a 400 before the route handler ever runs.

function validateBody(schema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);

    if (!result.success) {
      return res.status(400).json({ errors: result.error.issues });
    }

    req.body = result.data;
    next();
  };
}

app.post('/users', validateBody(userSchema), createUser);

This keeps createUser focused purely on business logic, trusting that req.body has already been validated and normalized.

Validation vs Sanitization

Validation checks whether data meets the required rules and rejects it if not. Sanitization goes further, modifying input into a safe or normalized form (trimming whitespace, escaping HTML, lowercasing an email) rather than just rejecting it outright. Real APIs typically need both.

Common Mistakes

  • Trusting req.body fields directly without checking their type or presence before using them.
  • Returning a 500 error for invalid input instead of a 400, confusing client errors with server errors.
  • Duplicating the same validation logic across many routes instead of extracting reusable middleware.
  • Validating only the request body while ignoring params and query strings that also need checks.

Key Takeaways

  • Validate all incoming request data (body, params, query) before it reaches business logic.
  • Return 400 Bad Request with a clear message for invalid input, not a generic 500.
  • Schema-based validation libraries scale better than manual checks as field counts grow.
  • Validation middleware keeps route handlers focused purely on business logic.

Pro Tip

Adopt a schema validation library (Zod, Joi, or similar) as soon as an endpoint has more than two or three fields to check. The upfront cost is small, and the payoff, consistent error messages and far less manual if logic, compounds quickly across a growing API.