The Two Lines of Defense
Think of your data like a precious jewel that needs protection. You want multiple layers of security to ensure its safety and integrity:
Database Constraints: The Vault Door
Database constraints are like the physical security measures at a bank vault:
- Primary Keys: Like unique safety deposit box numbers
- Foreign Keys: Like access cards that must match specific doors
- Not Null: Like requiring ID before entering the vault
- Unique: Like ensuring no two safety deposit boxes have the same number
Example in Sequelize Migrations
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
departmentId: {
type: Sequelize.INTEGER,
references: { model: 'Departments' },
onDelete: 'SET NULL'
}
});
}
};
Model Validations: The Security System
Model validations are like the electronic security system that can perform more sophisticated checks:
Example in Sequelize Model
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
validate: {
isEmail: {
msg: 'Must be a valid email address'
},
containsOrg: {
msg: 'Email must be from company domain',
fn: function(value) {
if (!value.endsWith('@company.org')) {
throw new Error('Invalid email domain');
}
}
}
}
},
phoneNumber: {
type: DataTypes.STRING,
validate: {
is: /^\+1-\d{3}-\d{3}-\d{4}$/,
msg: 'Phone number must be in format: +1-XXX-XXX-XXXX'
}
}
});
return User;
};
Real-World Application: E-commerce User Registration
Let's see how both types of validation work together in a practical scenario:
Database Constraints (Migration)
// migrations/XXXXXX-create-user.js
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
email: {
type: Sequelize.STRING(255),
allowNull: false,
unique: true
},
passwordHash: {
type: Sequelize.STRING,
allowNull: false
},
lastLoginAt: {
type: Sequelize.DATE
}
});
}
};
Model Validations (Model)
// models/user.js
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
validate: {
isEmail: true,
async isUnique(value) {
const existingUser = await User.findOne({ where: { email: value }});
if (existingUser) {
throw new Error('Email already registered');
}
},
notContainsSpecialChars(value) {
if (/[<>]/.test(value)) {
throw new Error('Email contains invalid characters');
}
}
}
},
password: {
type: DataTypes.VIRTUAL,
validate: {
isStrongPassword(value) {
if (value.length < 8) {
throw new Error('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(value)) {
throw new Error('Password must contain an uppercase letter');
}
if (!/[0-9]/.test(value)) {
throw new Error('Password must contain a number');
}
}
}
}
});
return User;
};
When to Use Each
Use Database Constraints For:
- Data Integrity: Ensuring referential integrity between tables
- Uniqueness: Preventing duplicate records
- Required Fields: Ensuring essential data is present
- Type Checking: Basic data type validation
Use Model Validations For:
- Complex Pattern Matching: Email formats, phone numbers, postal codes
- Business Logic: Age restrictions, password requirements
- Custom Validation Rules: Domain-specific validations
- Cross-field Validation: Comparing multiple fields
Best Practices
- Layer Your Defense: Implement both types where possible
- Fail Early: Model validations should catch issues before hitting the database
- Be Specific: Use precise error messages in model validations
- Performance Conscious: Consider the cost of complex validations
Common Pitfalls to Avoid
- Relying solely on model validations
- Duplicate complex validations at both levels
- Overlooking database-level race conditions
- Not handling validation errors properly in your application
Further Topics to Explore
- Custom validation functions
- Async validations
- Transaction-level constraints
- Validation hooks and lifecycle events