Database Constraints and Model Validations in Sequelize

Understanding the Problem

We need to implement data validation for a Colors table with specific requirements for the name field:

We'll be working with both database-level constraints and model-level validations to achieve this.

Devising a Plan

  1. Set up database-level constraints in migration file
  2. Implement model-level validations
  3. Test implementation with various inputs

Carrying Out the Plan

Step 1: Database Migration File

// Migration file (20211011170805-create-color.js)
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('Colors', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      name: {
        type: Sequelize.STRING(20),
        allowNull: false,  // Database constraint: No NULL values
        unique: true      // Database constraint: No duplicates
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal("CURRENT_TIMESTAMP")
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal("CURRENT_TIMESTAMP")
      }
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('Colors');
  }
};

Step 2: Model Validations

// Model file (color.js)
'use strict';
const { Model } = require('sequelize');

module.exports = (sequelize, DataTypes) => {
  class Color extends Model {
    static associate(models) {
      // Define associations here if needed
    }
  }
  
  Color.init({
    name: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        // Basic type validation
        isString(value) {
          if (typeof value !== 'string') {
            throw new Error('name must be a string');
          }
        },
        // Length validation
        len: {
          args: [2, 20],
          msg: 'name must be between 2 and 20 characters'
        },
        // Custom validation for 'y' ending
        noEndingInY(value) {
          if (value.slice(-1) === 'y') {
            throw new Error('name must not end in y');
          }
        }
      }
    }
  }, {
    sequelize,
    modelName: 'Color',
  });
  
  return Color;
};

Looking Back and Understanding

Database Constraints vs Model Validations

Think of database constraints like the security guards at a building's entrance, while model validations are like the receptionist who checks your appointment before you even leave home.

Database Constraints:

Model Validations:

Testing Examples

// Example test cases and their outcomes:
await Color.create({})               // Fails: name cannot be null
await Color.create({name: []})       // Fails: name must be a string
await Color.create({name: 'a'})      // Fails: name too short
await Color.create({name: 'orangey'})// Fails: ends in 'y'
await Color.create({name: 'orange'}) // Succeeds!

Real-World Applications

This pattern is commonly used in many real-world scenarios:

Common Pitfalls and Tips

Further Learning

To deepen your understanding, try: