Input Validation & Data Sanitization with Joi or Zod0%
Input Validation & Data Sanitization with Joi or Zod
Beginner12 min read•Updated: Jul 11, 2026
Input Validation & Data Sanitization with Joi or Zod
Never trust client input. Accepting unsanitized, unvalidated data directly into database queries or business operations leads to SQL/NoSQL injection, cross-site scripting (XSS), prototype pollution, and application crashes. In modern Node.js and TypeScript architectures, Zod has emerged as the premier schema validation library.
1. Why Zod?
- TypeScript-First: Static type inference is derived directly from the runtime schema (
z.infer<typeof schema>), eliminating duplicate type declarations. - Zero Dependencies: Extremely fast, lightweight, and works seamlessly on both Node.js backends and browser frontends.
- Composable & Declarative: Intuitive chainable validators for strings, numbers, arrays, and nested objects.
2. Defining a Zod Validation Schema
JavaScript
import { z } from 'zod';
export const registerUserSchema = z.object({
body: z.object({
username: z
.string({ required_error: 'Username is required' })
.min(3, 'Username must be at least 3 characters')
.max(30, 'Username cannot exceed 30 characters')
.regex(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores'),
email: z
.string({ required_error: 'Email is required' })
.email('Invalid email address format')
.toLowerCase()
.trim(),
password: z
.string({ required_error: 'Password is required' })
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[0-9]/, 'Password must contain at least one number'),
age: z
.number()
.int()
.min(18, 'Must be at least 18 years old')
.optional()
})
});
3. Creating a Reusable Validation Middleware
JavaScript
// src/middleware/validate.js
export const validate = (schema) => async (req, res, next) => {
try {
// Validate params, query, and body against the schema
const parsed = await schema.parseAsync({
body: req.body,
query: req.query,
params: req.params
});
// Replace req properties with clean, sanitized, coerced data
req.body = parsed.body;
req.query = parsed.query;
req.params = parsed.params;
next();
} catch (error) {
if (error.name === 'ZodError') {
const formattedErrors = error.errors.map(err => ({
field: err.path.join('.').replace('body.', ''),
message: err.message
}));
return res.status(400).json({
status: 'fail',
message: 'Validation failed',
errors: formattedErrors
});
}
next(error);
}
};
4. Applying Validation to Routes
JavaScript
import { Router } from 'express';
import { validate } from '../middleware/validate.js';
import { registerUserSchema } from '../schemas/user.schema.js';
import { registerUser } from '../controllers/auth.controller.js';
const router = Router();
// Route is protected: invalid payloads are rejected BEFORE hitting the controller!
router.post('/register', validate(registerUserSchema), registerUser);
export default router;
Interactive Quiz
Multiple Choice Questions
Question 1
What is the primary advantage of validating request input with a library like Zod or Joi before hitting the controller?
Question 2
How can TypeScript types be inferred directly from a Zod schema without writing manual interfaces?
Question 3
Which HTTP status code is standard when a request payload fails schema validation rules?
Question 4
What does the
.trim() and .toLowerCase() transformers on a Zod string accomplish?Question 5
What method on a Zod schema validates data asynchronously and throws a
ZodError if validation fails?Next Lesson
User Authentication with JWT (JSON Web Tokens) & HTTP-Only Cookies
Continue learning with hands-on practice, examples, and exercises in the upcoming topic.
Related Lessons
Practice Quiz
Test your understanding of this lesson with 5 questions. Each question has one correct answer.