Request Validation Middleware — Complete Implementation Guide
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
Only validating request bodies — Query parameters and URL parameters need validation too. Malformed query strings can crash your database queries.
Not sanitizing validated data — Use the validated value returned by the schema library, not the raw input, to prevent injection attacks.
Aborting on first error — Report all validation errors at once so the client can fix everything in one request instead of iterating.
Trusting parsed JSON types — JSON numbers can be floats, null, or strings. Always validate types even for supposedly typed fields.
Not handling array and nested object validation — Deeply nested objects need recursive schema validation. Use libraries that support nested schemas.
Practice Questions
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.
What is the benefit of schema-based validation over manual checks? Schemas are declarative, reusable, and produce consistent error formats with less code.
How do you validate URL parameters like /users/:id? Use a separate validation middleware or validate within the route handler for parameterized routes.
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
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