Skip to content

Request Validation Middleware — Complete Implementation Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Request Validation Middleware. We cover key concepts, practical examples, and best practices to help you master this topic.

Request validation middleware inspects incoming data against defined schemas before it reaches route handlers, preventing invalid data from corrupting your application state or causing errors.

What You'll Learn

By the end of this tutorial, you will build validation middleware that checks request bodies, query parameters, and URL parameters against schemas, returning clear error messages for invalid data.

Why It Matters

Unvalidated input is the leading cause of security vulnerabilities and runtime errors. DodaTech validates every API request at the middleware layer to catch malformed data before it reaches business logic.

Real-World Use

DodaZIP's file conversion API validates file types, sizes, and conversion parameters in middleware before passing requests to the conversion engine, preventing errors and resource abuse.

Validation Middleware Learning Path

flowchart LR
  A[Error Middleware] --> B[Validation Middleware]
  B --> C[Schema Libraries]
  C --> D[Sanitization]
  B --> E{You Are Here}
  style E fill:#f90,color:#fff

Manual Field Validation

The simplest validation middleware checks each required field manually, returning specific error messages for missing or invalid values.

const express = require("express");
const app = express();

app.use(express.json());

function validateUser(req, res, next) {
  const errors = [];

  if (!req.body.name || typeof req.body.name !== "string") {
    errors.push("Name is required and must be a string");
  }

  if (!req.body.email || !req.body.email.includes("@")) {
    errors.push("Valid email is required");
  }

  if (req.body.age !== undefined) {
    if (typeof req.body.age !== "number" || req.body.age < 0) {
      errors.push("Age must be a positive number");
    }
  }

  if (errors.length > 0) {
    return res.status(400).json({ errors });
  }

  next();
}

app.post("/users", validateUser, (req, res) => {
  res.status(201).json({ created: true, user: req.body });
});

app.listen(3000);

Expected output for POST /users with empty body:

{"errors": ["Name is required and must be a string", "Valid email is required"]}

Schema-Based Validation with Joi

Joi is a popular schema validation library that lets you define validation rules declaratively, reducing boilerplate and improving readability.

const Joi = require("joi");
const express = require("express");
const app = express();

app.use(express.json());

const userSchema = Joi.object({
  name: Joi.string().min(2).max(50).required(),
  email: Joi.string().email().required(),
  age: Joi.number().integer().min(0).max(150).optional(),
  role: Joi.string().valid("user", "admin", "moderator").default("user")
});

function validate(schema) {
  return (req, res, next) => {
    const { error, value } = schema.validate(req.body, { abortEarly: false });

    if (error) {
      const errors = error.details.map(d => ({
        field: d.path.join("."),
        message: d.message
      }));
      return res.status(400).json({ errors });
    }

    req.body = value;
    next();
  };
}

app.post("/users", validate(userSchema), (req, res) => {
  res.status(201).json({ created: true, data: req.body });
});

app.listen(3000);

Expected output for POST /users with invalid data:

{"errors": [{"field": "name", "message": "\"name\" is required"}, {"field": "email", "message": "\"email\" is required"}]}

Query Parameter Validation

Validation middleware can also check query parameters, ensuring required query strings are present and correctly formatted.

function validateQueryParams(req, res, next) {
  const errors = [];

  if (req.query.page) {
    const page = Number(req.query.page);
    if (!Number.isInteger(page) || page < 1) {
      errors.push("Page must be a positive integer");
    }
  }

  if (req.query.limit) {
    const limit = Number(req.query.limit);
    if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
      errors.push("Limit must be between 1 and 100");
    }
  }

  if (req.query.sort) {
    const allowed = ["name", "date", "price"];
    if (!allowed.includes(req.query.sort)) {
      errors.push(`Sort must be one of: ${allowed.join(", ")}`);
    }
  }

  if (errors.length > 0) {
    return res.status(400).json({ errors });
  }

  next();
}

app.get("/products", validateQueryParams, (req, res) => {
  res.json({ products: [], pagination: { page: req.query.page || 1 } });
});

Expected output for GET /products?sort=invalid:

{"errors": ["Sort must be one of: name, date, price"]}

Common Mistakes

  1. Only validating request bodies — Query parameters and URL parameters need validation too. Malformed query strings can crash your database queries.

  2. Not sanitizing validated data — Use the validated value returned by the schema library, not the raw input, to prevent injection attacks.

  3. Aborting on first error — Report all validation errors at once so the client can fix everything in one request instead of iterating.

  4. Trusting parsed JSON types — JSON numbers can be floats, null, or strings. Always validate types even for supposedly typed fields.

  5. Not handling array and nested object validation — Deeply nested objects need recursive schema validation. Use libraries that support nested schemas.

Practice Questions

  1. Why should validation run in middleware rather than in route handlers? Middleware keeps validation logic centralized and reusable. Route handlers stay focused on business logic.

  2. What is the benefit of schema-based validation over manual checks? Schemas are declarative, reusable, and produce consistent error formats with less code.

  3. How do you validate URL parameters like /users/:id? Use a separate validation middleware or validate within the route handler for parameterized routes.

  4. Challenge: Build a generic validation middleware that supports multiple schemas for different routes.

function validateBody(schema) {
  return (req, res, next) => {
    const { error, value } = schema.validate(req.body);
    if (error) return res.status(400).json({ error: error.details[0].message });
    req.body = value;
    next();
  };
}
app.post("/users", validateBody(userSchema), handler);
app.post("/products", validateBody(productSchema), handler);

FAQ

Should I validate on the client and server?

Yes. Client validation improves UX, but server validation is mandatory for security. Never trust client-side validation alone.

What is the best validation library for Node.js?

Joi is the most popular. Zod is gaining traction for TypeScript projects. Express-validator integrates directly with Express.

How do I validate file uploads in middleware?

Check file type (MIME), size, and extension in middleware before passing to the upload handler. Use libraries like multer.

Can validation middleware modify the request body?

Yes. Schema-based validators can sanitize and transform data, setting defaults and stripping unknown fields.

How do I handle optional fields with defaults?

Use the schema library's default feature. When a field is omitted, the schema assigns the default value automatically.

Mini Project

Build a complete validation middleware system with body, query, and parameter validation using Joi schemas for a product API.

const express = require("express");
const Joi = require("joi");
const app = express();

app.use(express.json());

const productSchema = Joi.object({
  name: Joi.string().min(2).max(100).required(),
  price: Joi.number().positive().required(),
  category: Joi.string().valid("electronics", "books", "clothing").required(),
  inStock: Joi.boolean().default(true)
});

const querySchema = Joi.object({
  page: Joi.number().integer().min(1).default(1),
  limit: Joi.number().integer().min(1).max(100).default(20),
  category: Joi.string().valid("electronics", "books", "clothing").optional()
});

function validateBody(schema) {
  return (req, res, next) => {
    const { error, value } = schema.validate(req.body);
    if (error) return res.status(400).json({ error: error.details[0].message });
    req.body = value;
    next();
  };
}

function validateQuery(schema) {
  return (req, res, next) => {
    const { error, value } = schema.validate(req.query);
    if (error) return res.status(400).json({ error: error.details[0].message });
    req.query = value;
    next();
  };
}

app.post("/api/products", validateBody(productSchema), (req, res) => {
  res.status(201).json({ created: req.body });
});

app.get("/api/products", validateQuery(querySchema), (req, res) => {
  res.json({ filters: req.query });
});

app.listen(3000);

What's Next

Now that you understand request validation middleware, explore compressing responses with middleware. Then learn about handling cross-origin requests with middleware.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro