Node.js Input Validation
Input validation checks every piece of data before your app uses it. Visitors make typing mistakes, and attackers send harmful data on purpose. A good check blocks both problems at the door.
The Security Guard Idea
Picture a guard at a building entrance. The guard checks each visitor's badge before opening the door. Validation plays that role for your routes.
Request --> [ Validation Guard ] --> Route logic --> Database
|
+--> Invalid? Reject with a clear message
What to Check
- Presence: the field exists and is not empty.
- Type: a number is a number, and text is text.
- Length: the value fits a minimum and maximum size.
- Format: emails, phone numbers, and dates follow a pattern.
- Range: an age sits between sensible limits.
Manual Checks
Small apps can validate with plain JavaScript.
app.post("/register", (req, res) => {
const { name, age } = req.body;
const errors = [];
if (typeof name !== "string" || name.trim().length < 2) {
errors.push("Name needs at least 2 letters.");
}
if (!Number.isInteger(age) || age < 13 || age > 120) {
errors.push("Age must be a whole number from 13 to 120.");
}
if (errors.length > 0) {
return res.status(400).json({ errors });
}
res.status(201).json({ message: "Registered" });
});Manual code grows long in bigger projects. A validation library keeps rules tidy.
Validate with Joi
Joi describes the rules in one schema object. The schema works like a blueprint for valid data.
npm install joi
const Joi = require("joi");
const userSchema = Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(13).max(120),
role: Joi.string().valid("student", "teacher").default("student")
});
app.post("/users", (req, res) => {
const { error, value } = userSchema.validate(req.body, { abortEarly: false });
if (error) {
const messages = error.details.map((d) => d.message);
return res.status(400).json({ errors: messages });
}
res.status(201).json(value);
});The option abortEarly: false reports every problem at once, so visitors fix everything in one try.
Reusable Validation Middleware
Middleware lets you apply one schema to any route with a single line.
function validate(schema) {
return (req, res, next) => {
const { error, value } = schema.validate(req.body, { abortEarly: false });
if (error) {
return res.status(400).json({
errors: error.details.map((d) => d.message)
});
}
req.body = value;
next();
};
}
app.post("/users", validate(userSchema), (req, res) => {
res.status(201).json(req.body);
});
Request --> validate(userSchema) --> route handler
|
+--> 400 response with error list
Validate with express-validator
The express-validator library places rules directly inside route definitions.
const { body, validationResult } = require("express-validator");
app.post(
"/signup",
body("email").isEmail().normalizeEmail(),
body("password").isLength({ min: 8 }),
(req, res) => {
const result = validationResult(req);
if (!result.isEmpty()) {
return res.status(400).json({ errors: result.array() });
}
res.send("OK");
}
);Validate More Than the Body
- req.params holds values from the path, such as an item id.
- req.query holds search words, such as page numbers.
- req.headers holds details sent with the request.
app.get("/items/:id", (req, res) => {
const id = Number(req.params.id);
if (!Number.isInteger(id) || id < 1) {
return res.status(400).send("Item id must be a positive whole number.");
}
res.send("Item " + id);
});Clean the Data
Sanitizing changes input into a safe, tidy form. Trim spaces, convert emails to lowercase, and remove unwanted fields. Joi removes unknown keys with the option stripUnknown: true.
Client and Server Checks
Browser checks give quick feedback. Attackers skip the browser and call your server directly. Always validate on the server.
Practice Tasks
- Write a Joi schema for a product with a name, price, and stock count.
- Reject negative prices with a helpful message.
- Validate a page number in the query string and default it to 1.
