Saas.js logo
Drizzle CRUD/Advanced

Lifecycle Hooks

Add custom business logic with lifecycle hooks in Drizzle CRUD

Lifecycle Hooks

Lifecycle hooks allow you to inject custom business logic at specific points in the CRUD operations lifecycle. This is perfect for adding timestamps, validation, transformations, and other business rules.

Available Hooks

Data Transformation Hooks

  • beforeCreate - Transform data before creating
  • beforeUpdate - Transform data before updating

Validation Hooks

  • validate - Custom validation logic

Operation Hooks

  • beforeOperation - Run before any operation
  • afterOperation - Run after any operation

Basic Usage

const userCrud = createCrud(users, {
  hooks: {
    beforeCreate: (data) => ({
      ...data,
      email: data.email.toLowerCase(),
      createdAt: new Date(),
    }),

    beforeUpdate: (data) => ({
      ...data,
      updatedAt: new Date(),
    }),

    validate: ({ data, context, operation }) => {
      // Custom validation logic
      return !context?.skipValidation
    },
  },
})

Data Transformation Hooks

Before Create

Transform data before insertion:

const userCrud = createCrud(users, {
  hooks: {
    beforeCreate: (data) => ({
      ...data,
      // Normalize email
      email: data.email.toLowerCase().trim(),
      // Generate slug from name
      slug: data.name.toLowerCase().replace(/\s+/g, '-'),
      // Set default values
      isActive: data.isActive ?? true,
      createdAt: new Date(),
    }),
  },
})

Before Update

Transform data before updating:

const postCrud = createCrud(posts, {
  hooks: {
    beforeUpdate: (data) => ({
      ...data,
      // Update slug if title changed
      slug: data.title
        ? data.title.toLowerCase().replace(/\s+/g, '-')
        : undefined,
      // Always update timestamp
      updatedAt: new Date(),
    }),
  },
})

Validation Hooks

Custom validation beyond schema validation:

const userCrud = createCrud(users, {
  hooks: {
    validate: ({ data, context, operation }) => {
      // Skip validation for trusted sources
      if (context?.skipValidation) {
        return true
      }

      // Custom business rules
      if (operation === 'create' && data.email) {
        const isValidDomain = data.email.endsWith('@company.com')
        if (!isValidDomain) {
          throw new Error('Only company email addresses are allowed')
        }
      }

      // Age validation
      if (data.age && data.age < 13) {
        throw new Error('Users must be at least 13 years old')
      }

      return true
    },
  },
})

Hook Context

Hooks receive context information:

interface HookContext {
  operation: 'create' | 'update' | 'delete' | 'list' | 'findById'
  data?: any // The data being operated on
  result?: any // The result (for after hooks)
  originalData?: any // Original data (for update operations)
  context?: OperationContext // The operation context
}

Using Hook Context

const smartCrud = createCrud(articles, {
  hooks: {
    beforeCreate: (data, { context }) => {
      // Set author from actor
      if (context?.actor?.type === 'user') {
        data.authorId = context.actor.properties.userId
      }

      return data
    },

    validate: ({ data, context, operation }) => {
      return context?.skipValidation ?? true
    },
  },
})

Next Steps