Data Validation Techniques
Master input validation, sanitization, type checking, and schema validation to build secure applications.
Table of Contents
Why Validation Matters
Data validation ensures that user input meets expectations before processing. Without validation, applications are vulnerable to crashes, data corruption, and security breaches like SQL injection or XSS attacks.
Validation is your first line of defense against bad data and malicious attacks. It prevents:
- Data corruption: Invalid data entering your database
- Security vulnerabilities: SQL injection, XSS, command injection
- Application crashes: Unexpected input causing errors
- Poor user experience: Confusing error messages
Never trust user input. Always validate on both client and server side. Client-side validation improves UX; server-side validation ensures security.
Types of Validation
1. Type Validation
Ensure data is the correct type:
// JavaScript
function isNumber(value) {
return typeof value === 'number' && !isNaN(value);
}
// TypeScript provides compile-time type checking
function processAge(age: number): void {
if (age < 0 || age > 150) {
throw new Error('Invalid age');
}
}
2. Format Validation
Check data matches expected patterns:
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new Error('Invalid email format');
}
// Phone validation
const phoneRegex = /^\d{3}-\d{3}-\d{4}$/;
// URL validation
try {
new URL(urlString);
} catch {
throw new Error('Invalid URL');
}
3. Range Validation
Verify values are within acceptable bounds:
// Age between 18 and 100
if (age < 18 || age > 100) {
throw new Error('Age must be 18-100');
}
// String length
if (password.length < 8 || password.length > 128) {
throw new Error('Password must be 8-128 characters');
}
// Date range
const minDate = new Date('2020-01-01');
const maxDate = new Date();
if (date < minDate || date > maxDate) {
throw new Error('Invalid date range');
}
4. Required Field Validation
function validateRequired(data, fields) {
const missing = fields.filter(field => !data[field]);
if (missing.length > 0) {
throw new Error(`Missing fields: ${missing.join(', ')}`);
}
}
Sanitization vs Validation
Validation: Check if input is acceptable (reject if not)
Sanitization: Clean input to make it safe
// Remove HTML tags
const sanitized = input.replace(/<[^>]*>/g, '');
// Escape HTML entities
function escapeHtml(text) {
return text
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}
// Trim whitespace
const cleaned = input.trim();
Schema Validation
Use libraries to validate complex data structures:
// Joi (Node.js)
const Joi = require('joi');
const schema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(18).max(100),
password: Joi.string().pattern(/^[a-zA-Z0-9]{8,30}$/)
});
const { error, value } = schema.validate(userData);
if (error) {
console.error(error.details);
}
// Zod (TypeScript)
import { z } from 'zod';
const userSchema = z.object({
username: z.string().min(3).max(30),
email: z.string().email(),
age: z.number().int().min(18).max(100),
password: z.string().min(8).regex(/[A-Z]/).regex(/[a-z]/).regex(/[0-9]/)
});
const result = userSchema.safeParse(userData);
if (!result.success) {
console.error(result.error.issues);
}
JSON Schema Validation
const schema = {
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 3 },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 18 }
},
"required": ["name", "email"]
};
// Use Ajv library to validate
const Ajv = require('ajv');
const ajv = new Ajv();
const validate = ajv.compile(schema);
const valid = validate(data);
Security Considerations
SQL Injection Prevention
Never concatenate user input into SQL queries:
// ❌ VULNERABLE
const query = `SELECT * FROM users WHERE email = '${email}'`;
// ✅ SAFE - Use parameterized queries
const query = 'SELECT * FROM users WHERE email = ?';
db.execute(query, [email]);
// ✅ SAFE - Use ORM
const user = await User.findOne({ where: { email } });
XSS (Cross-Site Scripting) Prevention
Escape user input before displaying in HTML:
// ❌ VULNERABLE
document.getElementById('output').innerHTML = userInput;
// ✅ SAFE - Use textContent
document.getElementById('output').textContent = userInput;
// ✅ SAFE - Use DOMPurify library
const clean = DOMPurify.sanitize(userInput);
document.getElementById('output').innerHTML = clean;
Command Injection Prevention
// ❌ VULNERABLE
const { exec } = require('child_process');
exec(`ls ${userInput}`);
// ✅ SAFE - Validate and whitelist
const allowedCommands = ['ls', 'pwd', 'date'];
if (!allowedCommands.includes(userInput)) {
throw new Error('Invalid command');
}
class UserValidator {
static validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!regex.test(email)) {
throw new Error('Invalid email format');
}
return email.toLowerCase().trim();
}
static validatePassword(password) {
if (password.length < 8) {
throw new Error('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
throw new Error('Password must contain uppercase letter');
}
if (!/[a-z]/.test(password)) {
throw new Error('Password must contain lowercase letter');
}
if (!/[0-9]/.test(password)) {
throw new Error('Password must contain number');
}
return password;
}
static validateUser(data) {
const errors = {};
try {
data.email = this.validateEmail(data.email);
} catch (e) {
errors.email = e.message;
}
try {
data.password = this.validatePassword(data.password);
} catch (e) {
errors.password = e.message;
}
if (Object.keys(errors).length > 0) {
throw { errors };
}
return data;
}
}
Frontend Validation
HTML5 provides built-in validation attributes:
Custom JavaScript Validation
const form = document.querySelector('form');
form.addEventListener('submit', (e) => {
e.preventDefault();
const email = form.email.value;
const emailError = document.getElementById('email-error');
if (!email.includes('@')) {
emailError.textContent = 'Invalid email';
emailError.style.display = 'block';
return;
}
emailError.style.display = 'none';
// Submit form
});
Client-side: Fast feedback, better UX, can be bypassed
Server-side: Secure, authoritative, slower feedback
Best practice: Use both!
Backend Validation Best Practices
- Validate early: Check inputs before processing
- Use whitelists: Allow known good values, not block known bad
- Fail securely: Return generic error messages to users
- Log validation failures: Monitor for attack patterns
- Use parameterized queries: Prevent SQL injection
Common Validation Libraries
| Language | Library | Features |
|---|---|---|
| JavaScript | Joi, Yup, Zod | Schema validation, TypeScript support |
| Python | Pydantic, Marshmallow | Data classes, serialization |
| Java | Hibernate Validator | Bean validation (JSR 380) |
| PHP | Respect\Validation | Fluent interface, 150+ rules |
Testing Validation Logic
// Unit test example (Jest)
describe('UserValidator', () => {
test('rejects invalid email', () => {
expect(() => {
UserValidator.validateEmail('invalid');
}).toThrow('Invalid email format');
});
test('accepts valid email', () => {
const result = UserValidator.validateEmail('[email protected]');
expect(result).toBe('[email protected]');
});
test('rejects weak password', () => {
expect(() => {
UserValidator.validatePassword('weak');
}).toThrow();
});
});
- Always validate on server-side (client-side can be bypassed)
- Validate early and return clear error messages
- Use schema validation libraries for complex data
- Sanitize output when displaying user data
- Never trust user input—validate everything
- Whitelist allowed values instead of blacklisting bad ones
- Test your validation logic thoroughly
- Log validation failures for security monitoring
Data validation is not just about preventing errors—it's a critical security practice. By validating and sanitizing all user input, you protect your application from attacks and ensure data integrity throughout your system.