npm package: @foryourdev/nestjs-crud
Description: Automatically generate CRUD Rest API based on NestJS and TypeOrm
Latest version: 0.3.21

# nestjs-crud

[![npm version](https://badge.fury.io/js/nestjs-crud.svg)](https://badge.fury.io/js/nestjs-crud)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A powerful library that automatically generates RESTful CRUD APIs based on NestJS and TypeORM.

## 📋 Table of Contents

-   [Features](#features)
-   [Installation](#installation)
-   [Quick Start](#quick-start)
-   [Basic CRUD Operations](#basic-crud-operations)
-   [RESTful Query Parameters](#restful-query-parameters)
-   [Advanced Configuration](#advanced-configuration)
    -   [Security Control Settings](#security-control-settings)
    -   [Lifecycle Hooks](#lifecycle-hooks)
        -   [Decorator Approach (NEW! Recommended)](#🎯-method-1-decorator-approach-new--recommended)
        -   [Routes Configuration Approach (Legacy)](#🛠️-method-2-routes-configuration-approach-legacy)
    -   [Response Helpers (crudResponse)](#📋-response-helpers-crudresponse)
    -   [CrudQueryHelper for Custom Routes](#crudqueryhelper-for-custom-routes)
-   [Soft Delete and Recovery](#🗑️-soft-delete-and-recovery)
-   [Migration Guide](#migration-guide)
-   [API Documentation](#api-documentation)
-   [Examples](#examples)
-   [License](#license)

## ✨ Features

### 🚀 Core Features

-   **Automatic CRUD Route Generation**: Auto-generate APIs based on TypeORM entities
-   **RESTful Standard Compliance**: API endpoints following industry standards
-   **Automatic Swagger Generation**: Auto-generate and maintain API documentation
-   **Powerful Validation**: Data validation through class-validator
-   **Full TypeScript Support**: Type safety and IntelliSense support

### 🔍 Advanced Query Features

-   **Filtering**: Support for 30+ filter operators
-   **Sorting**: Multi-field sorting support
-   **Relation Inclusion**: Relationship data loading with nested relation support
-   **Pagination**: Support for Offset, Cursor, and Number-based pagination
-   **Search**: Complex search condition support

### 🛠 Database Features

-   **Soft Delete**: Mark data as deleted without actual deletion
-   **Recovery**: Recover soft-deleted data
-   **Upsert**: Update if exists, create if doesn't exist
-   **Lifecycle Hooks**: Execute custom logic at each stage of CRUD operations
    -   **Decorator Approach 🆕**: Intuitive method decorators like `@BeforeCreate()`, `@AfterUpdate()`, `@BeforeDestroy()`, `@BeforeRecover()`
    -   **Routes Configuration Approach**: Legacy `routes.hooks` configuration approach
-   **Full-Text Search 🆕**: PostgreSQL FTS support with GIN indexes and `plainto_tsquery`

### 🔒 Security and Control Features

-   **Filter Restrictions**: Only columns specified in allowedFilters can be filtered
-   **Parameter Restrictions**: Only columns specified in allowedParams can be used as request parameters
-   **Relation Inclusion Restrictions**: Only relations specified in allowedIncludes can be included
-   **Default Block Policy**: If not configured, all filtering/parameters/relation inclusion is blocked
-   **Field Exclusion 🆕**: Global and route-specific field exclusion from responses

### ⚡ Performance Features (v0.2.7+)

-   **Built-in Caching**: Memory, Redis, and multi-tier caching strategies
-   **Lazy Loading**: Load relations only when needed
-   **Auto Relation Detection**: Automatic N+1 query optimization
-   **Enhanced Services**: `CacheableCrudService` and `EnhancedCrudService` for advanced features

## 📦 Installation

```bash
npm install nestjs-crud
# or
yarn add nestjs-crud
```

### Required Dependencies

```bash
npm install @nestjs/common @nestjs/core typeorm class-validator class-transformer
```

## 🚀 Quick Start

### 1. Create Entity

```typescript
// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { IsString, IsEmail, IsOptional } from 'class-validator';

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    @IsString()
    name: string;

    @Column({ unique: true })
    @IsEmail()
    email: string;

    @Column({ nullable: true })
    @IsOptional()
    @IsString()
    bio?: string;

    @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
    createdAt: Date;

    @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
    updatedAt: Date;
}
```

### 2. Create Service

```typescript
// user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CrudService } from 'nestjs-crud';
import { User } from './user.entity';

@Injectable()
export class UserService extends CrudService<User> {
    constructor(
        @InjectRepository(User)
        repository: Repository<User>,
    ) {
        super(repository);
    }
}
```

### 3. Create Controller

```typescript
// user.controller.ts
import { Controller } from '@nestjs/common';
import { Crud, BeforeCreate } from 'nestjs-crud';
import { UserService } from './user.service';
import { User } from './user.entity';
import * as bcrypt from 'bcrypt';

@Controller('users')
@Crud({
    entity: User,
})
export class UserController {
    constructor(public readonly crudService: UserService) {}

    // 🆕 NEW! Add logic easily with lifecycle hook decorators
    @BeforeCreate()
    async hashPassword(body: any, context: any) {
        if (body.password) {
            body.password = await bcrypt.hash(body.password, 10);
        }
        return body;
    }
}
```

### 4. Module Configuration

```typescript
// user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { User } from './user.entity';

@Module({
    imports: [TypeOrmModule.forFeature([User])],
    controllers: [UserController],
    providers: [UserService],
})
export class UserModule {}
```

## 🎯 Basic CRUD Operations

The above configuration automatically generates the following API endpoints:

| HTTP Method | Endpoint             | Description             | Method Name |
| ----------- | -------------------- | ----------------------- | ----------- |
| **GET**     | `/users`             | Get list of users       | `index`     |
| **GET**     | `/users/:id`         | Get specific user       | `show`      |
| **POST**    | `/users`             | Create new user         | `create`    |
| **PUT**     | `/users/:id`         | Update user information | `update`    |
| **DELETE**  | `/users/:id`         | Delete user             | `destroy`   |
| **POST**    | `/users/upsert`      | Create or update user   | `upsert`    |
| **POST**    | `/users/:id/recover` | Recover deleted user    | `recover`   |

### 📊 Unified Response Structure

All CRUD operations provide a consistent response structure with metadata:

#### GET /users (index) - Pagination Response

```json
{
    "data": [
        { "id": 1, "name": "John Doe", "email": "john@example.com" },
        { "id": 2, "name": "Jane Smith", "email": "jane@example.com" },
        { "id": 3, "name": "Bob Johnson", "email": "bob@example.com" }
    ],
    "metadata": {
        "operation": "index",
        "timestamp": "2024-01-15T11:00:00.000Z",
        "affectedCount": 3,
        "includedRelations": ["department", "posts"],
        "pagination": {
            "type": "offset",
            "total": 150,
            "page": 1,
            "pages": 15,
            "offset": 10,
            "nextCursor": "eyJpZCI6M30="
        }
    }
}
```

#### GET /users (cursor pagination)

```json
{
    "data": [
        { "id": 4, "name": "Alice Brown", "email": "alice@example.com" },
        { "id": 5, "name": "Charlie Wilson", "email": "charlie@example.com" }
    ],
    "metadata": {
        "operation": "index",
        "timestamp": "2024-01-15T11:00:00.000Z",
        "affectedCount": 2,
        "pagination": {
            "type": "cursor",
            "total": 150,
            "limit": 2,
            "totalPages": 75,
            "nextCursor": "eyJpZCI6NX0="
        }
    }
}
```

#### GET /users/:id (show)

```json
{
    "data": {
        "id": 1,
        "name": "John Doe",
        "email": "john@example.com",
        "createdAt": "2024-01-15T10:30:00.000Z"
    },
    "metadata": {
        "operation": "show",
        "timestamp": "2024-01-15T11:00:00.000Z",
        "affectedCount": 1,
        "includedRelations": ["department"],
        "excludedFields": ["password"]
    }
}
```

#### POST /users (create)

```json
{
    "data": {
        "id": 1,
        "name": "John Doe",
        "email": "john@example.com",
        "createdAt": "2024-01-15T10:30:00.000Z"
    },
    "metadata": {
        "operation": "create",
        "timestamp": "2024-01-15T10:30:00.000Z",
        "affectedCount": 1
    }
}
```

#### PUT /users/:id (update)

```json
{
    "data": {
        "id": 1,
        "name": "John Doe Updated",
        "email": "john_updated@example.com",
        "updatedAt": "2024-01-15T11:00:00.000Z"
    },
    "metadata": {
        "operation": "update",
        "timestamp": "2024-01-15T11:00:00.000Z",
        "affectedCount": 1
    }
}
```

#### POST /users/upsert (upsert)

```json
{
    "data": {
        "id": 1,
        "name": "John Doe Upsert",
        "email": "john_upsert@example.com"
    },
    "metadata": {
        "operation": "upsert",
        "timestamp": "2024-01-15T11:00:00.000Z",
        "affectedCount": 1,
        "isNew": false // true: newly created, false: existing data updated
    }
}
```

#### DELETE /users/:id (destroy)

```json
{
    "data": {
        "id": 1,
        "name": "John Doe",
        "email": "john@example.com",
        "deletedAt": "2024-01-15T11:00:00.000Z"
    },
    "metadata": {
        "operation": "destroy",
        "timestamp": "2024-01-15T11:00:00.000Z",
        "affectedCount": 1,
        "wasSoftDeleted": true // true: soft delete, false: hard delete
    }
}
```

#### POST /users/:id/recover (recover)

```json
{
    "data": {
        "id": 1,
        "name": "John Doe",
        "email": "john@example.com",
        "deletedAt": null
    },
    "metadata": {
        "operation": "recover",
        "timestamp": "2024-01-15T11:00:00.000Z",
        "affectedCount": 1,
        "wasSoftDeleted": true // Whether it was soft deleted before recovery
    }
}
```

#### Multiple Creation (POST /users - array submission)

```json
{
    "data": [
        { "id": 1, "name": "John Doe", "email": "john@example.com" },
        { "id": 2, "name": "Jane Smith", "email": "jane@example.com" }
    ],
    "metadata": {
        "operation": "create",
        "timestamp": "2024-01-15T10:30:00.000Z",
        "affectedCount": 2
    }
}
```

### 📝 Bulk Operations Support

| Operation | Single | Bulk (Multiple) | Description |
|-----------|--------|-----------------|-------------|
| **CREATE** | ✅ Supported | ✅ Supported | Submit single object or array of objects |
| **UPDATE** | ✅ Supported | ✅ Supported | Submit array with ID for each item |
| **UPSERT** | ✅ Supported | ✅ Supported | Submit array of objects for bulk upsert |
| **DELETE** | ✅ Supported | ✅ Supported | Submit array of IDs or conditions |
| **RECOVER** | ✅ Supported | ✅ Supported | Submit array of IDs for bulk recovery |

#### Bulk Create Example

```bash
# Single creation
POST /users
{
    "name": "John Doe",
    "email": "john@example.com"
}

# Bulk creation (array submission)
POST /users
[
    { "name": "John Doe", "email": "john@example.com" },
    { "name": "Jane Smith", "email": "jane@example.com" },
    { "name": "Bob Johnson", "email": "bob@example.com" }
]
```

#### Bulk Update Example (NEW! 🆕)

```bash
# Single update
PUT /users/1
{
    "name": "John Updated",
    "email": "john.updated@example.com"
}

# Bulk update (array submission with IDs)
PUT /users
[
    { "id": 1, "name": "John Updated", "email": "john.updated@example.com" },
    { "id": 2, "name": "Jane Updated", "email": "jane.updated@example.com" },
    { "id": 3, "name": "Bob Updated", "email": "bob.updated@example.com" }
]
```

#### Bulk Upsert Example (NEW! 🆕)

```bash
# Single upsert
POST /users/upsert
{
    "id": 1,
    "name": "John Doe",
    "email": "john@example.com"
}

# Bulk upsert (array submission)
POST /users/upsert
[
    { "id": 1, "name": "John Doe", "email": "john@example.com" },  // Update if exists
    { "name": "New User", "email": "new@example.com" },             // Create if not exists
    { "id": 99, "name": "Another User", "email": "another@example.com" }
]
```

#### Bulk Delete Example (NEW! 🆕)

```bash
# Single delete
DELETE /users/1

# Bulk delete (array of IDs in query params)
DELETE /users?ids=1,2,3

# Or via request body
DELETE /users
{
    "ids": [1, 2, 3]
}
```

#### Bulk Recover Example (NEW! 🆕)

```bash
# Single recover
POST /users/1/recover

# Bulk recover (array of IDs)
POST /users/recover
{
    "ids": [1, 2, 3]
}
```

**Note**: All bulk operations support lifecycle hooks and will execute them for each item in the batch.

## 🔍 RESTful Query Parameters

### 📋 Filtering

#### ⚠️ Important: Query Parameter Format

nestjs-crud uses **underscore separator format**. MongoDB-style `$` operators are not supported.

```bash
# ✅ Correct format (underscore separator)
GET /users?filter[email_eq]=test@example.com
GET /users?filter[age_gte]=18
GET /users?filter[name_like]=%John%

# ❌ Unsupported format (MongoDB style)
GET /users?filter[email][$eq]=test@example.com     # Won't work
GET /users?filter[age][$gte]=18                    # Won't work
GET /users?filter[name][$like]=%John%              # Won't work
```

**Parsing method:**

-   `filter[field_operator]=value` → ✅ Works correctly
-   `filter[field][$operator]=value` → ❌ Filter is ignored

#### Basic Comparison Operators

```bash
# Equals
GET /users?filter[name_eq]=John Doe
GET /users?filter[age_eq]=25

# Not equals
GET /users?filter[status_ne]=inactive
GET /users?filter[role_ne]=admin
```

#### Size Comparison Operators

```bash
# Greater than/Greater than or equal
GET /users?filter[age_gt]=18
GET /users?filter[age_gte]=18

# Less than/Less than or equal
GET /users?filter[age_lt]=65
GET /users?filter[age_lte]=65

# Range
GET /users?filter[age_between]=18,65
GET /users?filter[salary_between]=30000,80000
```

#### String Pattern Operators

```bash
# LIKE pattern (case sensitive)
GET /users?filter[name_like]=%John%
GET /users?filter[email_like]=%@gmail.com

# ILIKE pattern (case insensitive)
GET /users?filter[name_ilike]=%JOHN%
GET /users?filter[email_ilike]=%GMAIL%

# Start/End patterns
GET /users?filter[name_start]=John
GET /users?filter[email_end]=.com

# Contains
GET /users?filter[bio_contains]=developer
```

#### Array/List Operators

```bash
# Include (IN)
GET /users?filter[id_in]=1,2,3,4,5
GET /users?filter[role_in]=admin,manager,user

# Exclude (NOT IN)
GET /users?filter[status_not_in]=deleted,banned
GET /users?filter[role_not_in]=guest
```

#### NULL/Existence Check Operators

```bash
# NULL check
GET /users?filter[deleted_at_null]=true
GET /users?filter[last_login_null]=true

# NOT NULL check
GET /users?filter[avatar_not_null]=true
GET /users?filter[email_verified_at_not_null]=true

# Existence check (not null and not empty string)
GET /users?filter[bio_present]=true

# Blank check (null or empty string)
GET /users?filter[middle_name_blank]=true
```

#### Relation Filtering

```bash
# Nested relation filtering
GET /posts?filter[author.name_like]=%John%
GET /posts?filter[author.department.name_eq]=Development
GET /comments?filter[post.author.role_eq]=admin
```

### 🔄 Sorting

```bash
# Single field sorting
GET /users?sort=name                    # Name ascending
GET /users?sort=-created_at             # Creation date descending

# Multi-field sorting
GET /users?sort=role,name,-created_at   # Role>Name>Creation date order

# Relation field sorting
GET /posts?sort=author.name,-created_at
GET /users?sort=department.name,name
```

### 🔗 Including Relations

**⚠️ Important Changes**:

-   The `routes.relations` option has been deprecated.
-   Now use `allowedIncludes` configuration and `include` query parameter together.
-   **Enhanced Security**: If allowedIncludes is not configured, all relation inclusion is blocked.

```bash
# Single relation (only relations allowed in allowedIncludes)
GET /users?include=department
GET /posts?include=author

# Multiple relations
GET /users?include=department,posts
GET /posts?include=author,comments

# Nested relations
GET /posts?include=author,comments.author
GET /users?include=department.company,posts.comments
GET /orders?include=customer.address,items.product.category
```

#### Before and After Comparison

```typescript
// ❌ Previous approach (deprecated)
@Crud({
  entity: User,
  routes: {
    index: {
      relations: ['department', 'posts'], // Relations included by default
    }
  }
})

// ✅ New approach (enhanced security)
@Crud({
  entity: User,
  allowedIncludes: ['department', 'posts'], // Explicitly specify allowed relations
  routes: {
    index: {
      allowedIncludes: ['department', 'posts', 'posts.comments'], // Additional method-specific allowances
    }
  }
})

// When relations are needed, explicitly request via query parameter
GET /users?include=department,posts
```

#### Security Policy

```typescript
// 1. allowedIncludes not configured → All relations blocked
@Crud({
  entity: User,
  // No allowedIncludes → All includes ignored
})

// 2. Global configuration
@Crud({
  entity: User,
  allowedIncludes: ['department'], // Only department allowed
})

// 3. Method-specific configuration (higher priority)
@Crud({
  entity: User,
  allowedIncludes: ['department'], // Global: department only
  routes: {
    index: {
      allowedIncludes: ['department', 'posts'], // INDEX allows posts additionally
    },
    show: {
      // No allowedIncludes → Uses global configuration: department only
    },
  },
})
```

#### Benefits

1. **Enhanced Security**: Only explicitly allowed relations can be included
2. **Explicit Requests**: Selectively load only necessary relations
3. **Performance Optimization**: Prevent unnecessary relation loading
4. **N+1 Problem Prevention**: Handle necessary relations with JOINs only
5. **Fine-grained Control**: Apply different relation inclusion policies per method

### 📄 Pagination

#### Page Number Method

```bash
GET /users?page[number]=1&page[size]=10     # Page 1, 10 items per page
GET /users?page[number]=3&page[size]=20     # Page 3, 20 items per page
```

#### Offset Method

```bash
GET /users?page[offset]=0&page[limit]=10    # First 10 items
GET /users?page[offset]=20&page[limit]=10   # 10 items starting from 20th
```

#### Cursor Method

```bash
GET /users?page[cursor]=eyJpZCI6MTB9&page[size]=10
```

### 📊 Pagination Response Structure

#### Offset/Number Pagination Response

```json
{
    "data": [
        { "id": 1, "name": "John Doe", "email": "john@example.com" },
        { "id": 2, "name": "Jane Smith", "email": "jane@example.com" }
    ],
    "metadata": {
        "page": 1, // Current page number
        "pages": 10, // Total pages ✅
        "total": 95, // Total data count
        "offset": 10, // Next offset
        "nextCursor": "..." // Next page token
    }
}
```

#### Cursor Pagination Response

```json
{
    "data": [
        { "id": 1, "name": "John Doe", "email": "john@example.com" },
        { "id": 2, "name": "Jane Smith", "email": "jane@example.com" }
    ],
    "metadata": {
        "total": 95, // Total data count
        "totalPages": 10, // Total pages ✅
        "limit": 10, // Page size
        "nextCursor": "..." // Next page token
    }
}
```

### 🔍 Complex Query Examples

Check out complex query usage through real-world use cases:

#### User Search Example

```bash
# Get 10 active adult users sorted by recent registration
GET /users?filter[status_eq]=active&
          filter[age_gte]=18&
          sort=-created_at&
          page[number]=1&page[size]=10
```

#### Post Search Example

```bash
# Get published posts by specific author with author information
GET /posts?filter[author.name_like]=%John%&
          filter[status_eq]=published&
          filter[created_at_gte]=2024-01-01&
          include=author,comments&
          sort=-created_at,title&
          page[number]=1&page[size]=20
```

#### Order Search Example

```bash
# Get completed orders with customer and order item information
GET /orders?filter[status_eq]=completed&
           filter[total_amount_gte]=50000&
           filter[created_at_between]=2024-01-01,2024-12-31&
           include=customer.address,items.product&
           sort=-created_at&
           page[offset]=0&page[limit]=50
```

## ⚙️ Advanced Configuration

### 🔒 Security Control Settings

#### Filter Restrictions - allowedFilters

```typescript
@Controller('users')
@Crud({
    entity: User,
    allowedFilters: ['name', 'email', 'status'], // Global: only these columns allowed for filtering
    routes: {
        index: {
            allowedFilters: ['name', 'email', 'status', 'createdAt'], // INDEX allows more columns
        },
        show: {
            allowedFilters: ['name'], // SHOW allows only name
        },
    },
})
export class UserController {
    constructor(public readonly crudService: UserService) {}
}
```

**Operation examples:**

```bash
# ✅ Allowed columns - works normally
GET /users?filter[name_like]=%John%
GET /users?filter[email_eq]=test@example.com

# ❌ Disallowed columns - filter ignored
GET /users?filter[password_eq]=secret  # Ignored if password not in allowedFilters
```

#### Parameter Restrictions - allowedParams

```typescript
@Controller('users')
@Crud({
    entity: User,
    allowedParams: ['name', 'email'], // Global: only these columns allowed as request parameters
    routes: {
        create: {
            allowedParams: ['name', 'email', 'status'], // CREATE allows additional status
        },
        update: {
            allowedParams: ['name'], // UPDATE allows only name
        },
        upsert: {
            // No allowedParams -> uses global configuration: name, email only
        },
    },
})
export class UserController {
    constructor(public readonly crudService: UserService) {}
}
```

**Operation examples:**

```typescript
// Configuration: allowedParams: ['name', 'email']

// ✅ Only allowed parameters are processed
POST /users
{
  "name": "John Doe",        // ✅ Processed
  "email": "john@test.com",  // ✅ Processed
  "password": "secret",      // ❌ Removed (not in allowedParams)
  "internal_id": 123         // ❌ Removed (not in allowedParams)
}

// Actually processed data:
{
  "name": "John Doe",
  "email": "john@test.com"
}
```

#### Relation Inclusion Restrictions - allowedIncludes

```typescript
@Controller('posts')
@Crud({
    entity: Post,
    allowedIncludes: ['author'], // Global: only author relation allowed
    routes: {
        index: {
            allowedIncludes: ['author', 'comments', 'tags'], // INDEX allows more relations
        },
        show: {
            allowedIncludes: ['author', 'comments.author'], // SHOW allows nested relations
        },
    },
})
export class PostController {
    constructor(public readonly crudService: PostService) {}
}
```

**Operation examples:**

```bash
# ✅ Only allowed relations are included
GET /posts?include=author           # ✅ Included
GET /posts?include=comments         # ✅ Included (in INDEX)
GET /posts?include=author,comments  # ✅ Both included

# ❌ Disallowed relations are ignored
GET /posts?include=author,likes,comments  # ✅ Only author,comments included (likes ignored)
GET /posts?include=profile               # ❌ All relations ignored (profile not allowed)
```

### 🎛️ CRUD Options Configuration

```typescript
@Controller('users')
@Crud({
    entity: User,
    only: ['index', 'show', 'create', 'update'], // Enable only specific methods
    allowedFilters: ['name', 'email', 'status'], // Allowed filter columns
    allowedParams: ['name', 'email', 'bio'], // Allowed request parameters
    allowedIncludes: ['department', 'posts'], // Allowed relation inclusions
    routes: {
        index: {
            paginationType: PaginationType.OFFSET,
            numberOfTake: 20,
            sort: Sort.DESC,
            softDelete: false,
            allowedFilters: ['name', 'email', 'status', 'createdAt'], // Method-specific filter settings
            allowedIncludes: ['department', 'posts', 'posts.comments'], // Method-specific relation settings
        },
        show: {
            softDelete: true,
            allowedFilters: ['name', 'email'], // SHOW has restrictive filtering
            allowedIncludes: ['department'], // SHOW has basic relations only
        },
        create: {
            hooks: {
                assignBefore: async (body, context) => {
                    // Email normalization
                    if (body.email) {
                        body.email = body.email.toLowerCase().trim();
                    }
                    return body;
                },
                saveAfter: async (entity, context) => {
                    // Send user creation event
                    await eventBus.publish('user.created', entity);
                    return entity;
                },
            },
        },
        update: {
            hooks: {
                assignBefore: async (body, context) => {
                    body.updatedAt = new Date();
                    return body;
                },
            },
        },
        destroy: {
            softDelete: true,
        },
    },
})
export class UserController {
    constructor(public readonly crudService: UserService) {}
}
```

### 🔄 Lifecycle Hooks

Execute custom logic at each stage of CRUD operations through lifecycle hooks.

#### Hook Types

| Hook            | Execution Point            | Purpose                          | Supported Routes              |
| --------------- | -------------------------- | -------------------------------- | ----------------------------- |
| `assignBefore`  | **Before** data assignment | Input validation, transformation | create, update, upsert, show* |
| `assignAfter`   | **After** data assignment  | Entity post-processing           | create, update, upsert, show* |
| `saveBefore`    | **Before** saving          | Final validation, business logic | create, update, upsert        |
| `saveAfter`     | **After** saving           | Notifications, event generation  | create, update, upsert        |
| `destroyBefore` | **Before** entity deletion | Permission check, cleanup prep   | destroy                       |
| `destroyAfter`  | **After** entity deletion  | Audit logs, external sync        | destroy                       |
| `recoverBefore` | **Before** entity recovery | Permission check, recovery prep  | recover                       |
| `recoverAfter`  | **After** entity recovery  | Audit logs, notifications        | recover                       |

**Note**: For `show` operation, `assignBefore` processes query parameters before entity lookup, and `assignAfter` processes the retrieved entity before returning it.

#### 🎯 Method 1: Decorator Approach (NEW! 🆕 Recommended)

**Use decorators on class methods for intuitive usage.**

##### Available Decorators

**Basic decorators:**

```typescript
@BeforeCreate()  // Execute before CREATE (assignBefore)
@AfterCreate()   // Execute after CREATE (saveAfter)
@BeforeUpdate()  // Execute before UPDATE (assignBefore)
@AfterUpdate()   // Execute after UPDATE (saveAfter)
@BeforeUpsert()  // Execute before UPSERT (assignBefore)
@AfterUpsert()   // Execute after UPSERT (saveAfter)
@BeforeDestroy() // Execute before DESTROY (destroyBefore)
@AfterDestroy()  // Execute after DESTROY (destroyAfter)
@BeforeRecover() // Execute before RECOVER (recoverBefore)
@AfterRecover()  // Execute after RECOVER (recoverAfter)
@BeforeShow()    // Execute before SHOW (assignBefore for show)
@AfterShow()     // Execute after SHOW (assignAfter for show)
```

**Consistent fine-grained control decorators:**

```typescript
@BeforeAssign('create' | 'update' | 'upsert' | 'show')  // Before assignment
@AfterAssign('create' | 'update' | 'upsert' | 'show')   // After assignment
@BeforeSave('create' | 'update' | 'upsert')    // Before saving
@AfterSave('create' | 'update' | 'upsert')     // After saving
@BeforeDestroy('destroy')                       // Before entity deletion
@AfterDestroy('destroy')                        // After entity deletion
@BeforeRecover('recover')                       // Before entity recovery
@AfterRecover('recover')                        // After entity recovery
```

**🆕 New 4-stage detailed decorators (clearer control):**

```typescript
// === ASSIGN stage (data assignment to entity) ===
@BeforeAssignCreate()  @BeforeAssignUpdate()  @BeforeAssignUpsert()  @BeforeAssignShow()  // Before assignment
@AfterAssignCreate()   @AfterAssignUpdate()   @AfterAssignUpsert()   @AfterAssignShow()   // After assignment

// === SAVE stage (database saving) ===
@BeforeSaveCreate()    @BeforeSaveUpdate()    @BeforeSaveUpsert()    // Before saving
@AfterSaveCreate()     @AfterSaveUpdate()     @AfterSaveUpsert()     // After saving

// === DESTROY stage (entity deletion) ===
@BeforeDestroyDestroy()  // Before entity deletion
@AfterDestroyDestroy()   // After entity deletion

// === RECOVER stage (entity recovery) ===
@BeforeRecoverRecover()  // Before entity recovery
@AfterRecoverRecover()   // After entity recovery
```

##### Real Usage Examples

**Using basic decorators:**

```typescript
import { Controller, Post, Put } from '@nestjs/common';
import { Crud, BeforeCreate, AfterCreate, BeforeUpdate, AfterUpdate } from 'nestjs-crud';
import { User } from './user.entity';
import { UserService } from './user.service';
import * as bcrypt from 'bcrypt';

@Crud({
    entity: User,
    allowedParams: ['name', 'email', 'password', 'phone'],
})
@Controller('users')
export class UserController {
    constructor(public readonly crudService: UserService) {}

    // 🔐 Hash password before CREATE
    @BeforeCreate()
    async hashPasswordOnCreate(body: any, context: any) {
        if (body.password) {
            console.log('CREATE: Hashing password...');
            body.password = await bcrypt.hash(body.password, 10);
        }

        // Set default values
        body.provider = body.provider || 'local';
        body.role = body.role || 'user';

        return body;
    }

    // 📧 Send welcome email after CREATE
    @AfterCreate()
    async sendWelcomeEmail(entity: User, context: any) {
        console.log(`New user created: ${entity.email} (ID: ${entity.id})`);

        // Welcome email sending logic
        // await this.emailService.sendWelcomeEmail(entity);

        return entity;
    }

    // 🔐 Hash password before UPDATE too
    @BeforeUpdate()
    async hashPasswordOnUpdate(body: any, context: any) {
        if (body.password) {
            console.log('UPDATE: Hashing password...');
            body.password = await bcrypt.hash(body.password, 10);
        }

        // Auto-set update time
        body.updatedAt = new Date();

        return body;
    }

    // 📝 Log user update after UPDATE
    @AfterUpdate()
    async logUserUpdate(entity: User, context: any) {
        console.log(`User update completed: ${entity.email} (ID: ${entity.id})`);

        // Record update log
        // await this.auditService.logUserUpdate(entity, context.request?.user);

        return entity;
    }

    // 🗑️ Permission check and cleanup preparation before DELETE
    @BeforeDestroy()
    async beforeDeleteUser(entity: User, context: any) {
        console.log(`DELETE permission check for: ${entity.email} (ID: ${entity.id})`);

        // Check delete permission
        const userId = context.request?.user?.id;
        if (entity.id !== userId) {
            const userRole = context.request?.user?.role;
            if (userRole !== 'admin') {
                throw new Error('Permission denied: Cannot delete other users');
            }
        }

        // Prevent deletion of locked users
        if (entity.status === 'locked') {
            throw new Error('Cannot delete locked user');
        }

        // Set deletion metadata
        entity.deletedBy = userId;
        entity.deletedAt = new Date();

        return entity;
    }

    // 🧹 Cleanup and notification after DELETE
    @AfterDestroy()
    async afterDeleteUser(entity: User, context: any) {
        console.log(`User deleted successfully: ${entity.email} (ID: ${entity.id})`);

        // Cleanup related data
        // await this.sessionService.revokeUserSessions(entity.id);
        // await this.tokenService.revokeUserTokens(entity.id);

        // Send deletion notification
        // await this.notificationService.notifyUserDeletion(entity);

        // Log deletion for audit
        // await this.auditService.logUserDeletion(entity, context.request?.user);

        return entity;
    }

    // 🚀 NEW! Permission check and preparation before RECOVER
    @BeforeRecover()
    async beforeRecoverUser(entity: User, context: HookContext<User>) {
        console.log(`RECOVER permission check for: ${entity.email} (ID: ${entity.id})`);

        // Check recover permission
        const userId = context.request?.user?.id;
        if (entity.deletedBy !== userId) {
            const userRole = context.request?.user?.role;
            if (userRole !== 'admin') {
                throw new Error('Permission denied: Cannot recover other users data');
            }
        }

        // Prevent recovery of certain users
        if (entity.status === 'banned') {
            throw new Error('Cannot recover banned user');
        }

        // Set recovery metadata
        entity.recoveredBy = userId;
        entity.recoveredAt = new Date();

        return entity;
    }

    // 🚀 NEW! Cleanup and notification after RECOVER
    @AfterRecover()
    async afterRecoverUser(entity: User, context: HookContext<User>) {
        console.log(`User recovered successfully: ${entity.email} (ID: ${entity.id})`);

        // Restore related data
        // await this.sessionService.enableUserSessions(entity.id);
        // await this.tokenService.reissueUserTokens(entity.id);

        // Send recovery notification
        // await this.notificationService.notifyUserRecovery(entity);

        // Log recovery for audit
        // await this.auditService.logUserRecovery(entity, context.request?.user);

        return entity;
    }
}
```

##### Execution Order and Parameters

**Hooks during Create process:**

```typescript
@BeforeCreate()  // = @BeforeAssign('create')
async beforeCreate(body: any, context: HookContext) {
  // body: request data
  // context: { operation: 'create', params: {}, currentEntity: undefined }
  return body; // Return modified body
}

@AfterCreate()   // = @AfterSave('create')
async afterCreate(entity: User, context: HookContext) {
  // entity: saved entity
  // context: { operation: 'create', params: {}, currentEntity: undefined }
  return entity; // Return modified entity
}
```

**Hooks during Update process:**

```typescript
@BeforeUpdate()  // = @BeforeAssign('update')
async beforeUpdate(entity: User, context: HookContext) {
  // 🚀 NEW: entity-based (full entity with ID)
  // entity: entity to update (includes all fields)
  // context: { operation: 'update', params: { id: 5 }, currentEntity: User }
  return entity;
}

@AfterUpdate()   // = @AfterSave('update')
async afterUpdate(entity: User, context: HookContext) {
  // entity: updated entity
  // context: { operation: 'update', params: { id: 5 }, currentEntity: User }
  return entity;
}
```

**🆕 NEW! Hooks during Destroy process:**

```typescript
@BeforeDestroy() // = @BeforeDestroy('destroy')
async beforeDestroy(entity: User, context: HookContext) {
  // 🚀 Entity-based (full entity with ID)
  // entity: entity to delete (includes all fields)
  // context: { operation: 'destroy', params: { id: 5 }, currentEntity: User }

  // Perfect for permission checks and cleanup preparation
  if (entity.status === 'locked') {
    throw new Error('Cannot delete locked user');
  }

  return entity;
}

@AfterDestroy()  // = @AfterDestroy('destroy')
async afterDestroy(entity: User, context: HookContext) {
  // entity: deleted entity (for cleanup and logging)
  // context: { operation: 'destroy', params: { id: 5 }, currentEntity: User }

  // Perfect for cleanup, notifications, and audit logs
  await this.cleanupUserData(entity.id);

  return entity;
}
```

**🆕 NEW! Hooks during Recover process:**

```typescript
@BeforeRecover() // = @BeforeRecover('recover')
async beforeRecover(entity: User, context: HookContext) {
  // 🚀 Entity-based (full soft-deleted entity with ID)
  // entity: entity to recover (includes all fields)
  // context: { operation: 'recover', params: { id: 5 }, currentEntity: User }

  // Perfect for permission checks and recovery preparation
  if (entity.status === 'banned') {
    throw new Error('Cannot recover banned user');
  }

  // Set recovery metadata
  entity.recoveredBy = context.request?.user?.id;
  entity.recoveredAt = new Date();

  return entity;
}

@AfterRecover()  // = @AfterRecover('recover')
async afterRecover(entity: User, context: HookContext) {
  // entity: recovered entity (for restoration and logging)
  // context: { operation: 'recover', params: { id: 5 }, currentEntity: User }

  // Perfect for data restoration, notifications, and audit logs
  await this.restoreUserData(entity.id);

  return entity;
}

@BeforeShow() // = @BeforeAssign('show')
async beforeShow(params: any, context: HookContext) {
  // params: request parameters for entity lookup
  // context: { operation: 'show', params: { id: 5 } }
  
  // Transform parameters before entity lookup
  if (typeof params.id === 'string') {
    params.id = parseInt(params.id, 10);
  }
  
  return params;
}

@AfterShow()  // = @AfterAssign('show')
async afterShow(entity: User, context: HookContext) {
  // entity: retrieved entity
  // context: { operation: 'show', params: { id: 5 } }
  
  // Perfect for masking sensitive data, adding computed fields
  if (entity.password) {
    entity.password = '***HIDDEN***';
  }
  
  // Add view tracking
  await this.trackView(entity.id);
  
  return entity;
}
```

##### Advanced Usage Examples

```typescript
@Crud({
    entity: Post,
    allowedParams: ['title', 'content', 'status'],
})
@Controller('posts')
export class PostController {
    constructor(public readonly crudService: PostService) {}

    @BeforeCreate()
    async beforeCreatePost(body: any, context: any) {
        // Auto-set user ID
        const userId = context.request?.user?.id;
        if (userId) {
            body.userId = userId;
        }

        // Auto-generate slug
        if (body.title && !body.slug) {
            body.slug = body.title
                .toLowerCase()
                .replace(/[^a-z0-9]/g, '-')
                .replace(/-+/g, '-')
                .replace(/^-|-$/g, '');
        }

        return body;
    }

    @BeforeSave('create')
    async validateBeforeSave(entity: Post, context: any) {
        // Check and resolve slug duplication
        const existingPost = await this.crudService.findBySlug(entity.slug);
        if (existingPost) {
            entity.slug = `${entity.slug}-${Date.now()}`;
        }

        return entity;
    }

    @AfterCreate()
    async afterCreatePost(entity: Post, context: any) {
        // Update search index
        // await this.searchService.indexPost(entity);

        // Notify about published post
        if (entity.status === 'published') {
            // await this.notificationService.notifyNewPost(entity);
            console.log(`New post published: ${entity.title}`);
        }

        return entity;
    }

    // Multiple hooks can be used together
    @BeforeUpdate()
    @BeforeUpsert()
    async beforeModify(data: any, context: any) {
        // Common logic for both UPDATE and UPSERT
        data.updatedAt = new Date();

        if (context.operation === 'upsert' && !context.currentEntity) {
            data.createdAt = new Date();
        }

        return data;
    }

    // 🆕 NEW! DESTROY hook example
    @BeforeDestroy()
    async beforeDelete(entity: Post, context: any) {
        console.log(`Preparing to delete post: ${entity.title} (ID: ${entity.id})`);

        // Check if user has permission to delete this post
        const userId = context.request?.user?.id;
        if (entity.userId !== userId) {
            const userRole = context.request?.user?.role;
            if (userRole !== 'admin' && userRole !== 'moderator') {
                throw new Error('Permission denied: Cannot delete other users posts');
            }
        }

        // Prevent deletion of published posts by regular users
        if (entity.status === 'published' && context.request?.user?.role !== 'admin') {
            throw new Error('Cannot delete published posts');
        }

        return entity;
    }

    @AfterDestroy()
    async afterDelete(entity: Post, context: any) {
        console.log(`Post deleted successfully: ${entity.title} (ID: ${entity.id})`);

        // Clean up related data
        // await this.commentService.deletePostComments(entity.id);
        // await this.tagService.removePostTags(entity.id);

        // Update search index
        // await this.searchService.removeFromIndex(entity.id);

        // Send notification
        // await this.notificationService.notifyPostDeletion(entity);

        return entity;
    }
}
```

**🆕 Using new 4-stage detailed decorators:**

```typescript
import { Controller } from '@nestjs/common';
import {
    Crud,
    BeforeAssignCreate, // Before assignment
    AfterAssignCreate, // After assignment
    BeforeSaveCreate, // Before saving
    AfterSaveCreate, // After saving
    BeforeAssignUpdate,
    AfterSaveUpdate,
} from 'nestjs-crud';
import { User } from './user.entity';
import { UserService } from './user.service';
import * as bcrypt from 'bcrypt';

@Crud({
    entity: User,
    allowedParams: ['name', 'email', 'password', 'phone'],
})
@Controller('users')
export class UserController {
    constructor(public readonly crudService: UserService) {}

    // 🔐 CREATE: Stage 1 - Before assignment (data validation and transformation)
    @BeforeAssignCreate()
    async validateAndTransformCreate(body: any, context: any) {
        console.log('1️⃣ CREATE before assignment: Data validation and transformation');

        if (body.password) {
            body.password = await bcrypt.hash(body.password, 10);
        }

        // Set default values
        body.provider = body.provider || 'local';
        body.role = body.role || 'user';

        return body;
    }

    // 🔧 CREATE: Stage 2 - After assignment (entity post-processing)
    @AfterAssignCreate()
    async postProcessCreate(entity: User, context: any) {
        console.log('2️⃣ CREATE after assignment: Entity post-processing');

        // Additional entity processing logic
        if (!entity.displayName) {
            entity.displayName = entity.name;
        }

        return entity;
    }

    // 🔍 CREATE: Stage 3 - Before saving (final validation)
    @BeforeSaveCreate()
    async finalValidateCreate(entity: User, context: any) {
        console.log('3️⃣ CREATE before saving: Final validation');

        // Duplicate email check
        const existing = await this.crudService.findOne({ where: { email: entity.email } });
        if (existing) {
            throw new Error('Email already exists');
        }

        return entity;
    }

    // 📧 CREATE: Stage 4 - After saving (follow-up processing)
    @AfterSaveCreate()
    async postSaveCreate(entity: User, context: any) {
        console.log('4️⃣ CREATE after saving: Follow-up processing');

        // Send welcome email
        // await this.emailService.sendWelcomeEmail(entity);

        // Send analytics event
        // await this.analyticsService.trackUserCreated(entity);

        return entity;
    }

    // 🔐 UPDATE: Before assignment
    @BeforeAssignUpdate()
    async beforeUpdateAssign(entity: User, context: any) {
        console.log('🔄 UPDATE before assignment: Process update entity');

        // 🚀 NEW: Now receives full entity with ID
        if (entity.password) {
            entity.password = await bcrypt.hash(entity.password, 10);
        }

        entity.updatedAt = new Date();
        return entity;
    }

    // 📝 UPDATE: After saving
    @AfterSaveUpdate()
    async afterUpdateSave(entity: User, context: any) {
        console.log('📝 UPDATE after saving: Handle update completion');

        // Record update log
        // await this.auditService.logUserUpdate(entity, context.request?.user);

        return entity;
    }
}
```

**Using consistent fine-grained control decorators:**

```typescript
@Controller('posts')
export class PostController {
    constructor(public readonly crudService: PostService) {}

    // Execute before assignment stage for all methods (create, update, upsert)
    @BeforeAssign('create')
    @BeforeAssign('update')
    @BeforeAssign('upsert')
    async commonPreProcess(body: any, context: any) {
        console.log(`🔧 ${context.operation.toUpperCase()} before assignment common processing`);

        // Common pre-processing logic
        body.updatedAt = new Date();
        if (context.operation === 'create') {
            body.createdAt = new Date();
        }

        return body;
    }

    // Execute after saving stage for all methods
    @AfterSave('create')
    @AfterSave('update')
    @AfterSave('upsert')
    async commonPostProcess(entity: any, context: any) {
        console.log(`✅ ${context.operation.toUpperCase()} after saving common processing`);

        // Common post-processing logic (search index update, cache refresh, etc.)
        // await this.searchService.updateIndex(entity);
        // await this.cacheService.invalidate(`post:${entity.id}`);

        return entity;
    }

    // 🆕 NEW! DESTROY-specific hooks
    @BeforeDestroy()
    async beforePostDelete(entity: any, context: any) {
        console.log(`🗑️ DESTROY before deletion: Preparing to delete ${entity.title}`);

        // Check deletion permissions
        const userId = context.request?.user?.id;
        if (entity.userId !== userId && context.request?.user?.role !== 'admin') {
            throw new Error('Permission denied');
        }

        // Set deletion metadata
        entity.deletedBy = userId;
        entity.deletedAt = new Date();

        return entity;
    }

    @AfterDestroy()
    async afterPostDelete(entity: any, context: any) {
        console.log(`🧹 DESTROY after deletion: Cleanup for ${entity.title}`);

        // Common cleanup logic
        // await this.searchService.removeFromIndex(entity.id);
        // await this.cacheService.invalidate(`post:${entity.id}`);
        // await this.notificationService.notifyDeletion(entity);

        return entity;
    }
}
```

##### 🔗 Chain Execution of Multiple Hooks

**When using the same decorator on multiple methods, they execute in definition order as a chain:**

```typescript
@Crud({
    entity: User,
    allowedParams: ['name', 'email', 'password'],
})
@Controller('users')
export class UserController {
    constructor(public readonly crudService: UserService) {}

    // 🔗 First CREATE hook
    @BeforeCreate()
    async validateData(body: any, context: any) {
        console.log('1️⃣ Validating data...');

        if (!body.email) {
            throw new Error('Email is required');
        }

        body.step1 = 'validated';
        return body; // ✅ Modified body passed to next hook
    }

    // 🔗 Second CREATE hook (receives result from first hook)
    @BeforeCreate()
    async hashPassword(body: any, context: any) {
        console.log('2️⃣ Hashing password...');
        console.log('Previous step result:', body.step1); // ✅ Outputs 'validated'

        if (body.password) {
            body.password = await bcrypt.hash(body.password, 10);
        }

        body.step2 = 'encrypted';
        return body; // ✅ Return final modified body
    }

    // 🔗 Third CREATE hook (receives result from second hook)
    @BeforeCreate()
    async setDefaults(body: any, context: any) {
        console.log('3️⃣ Setting defaults...');
        console.log('Previous steps results:', body.step1, body.step2); // ✅ Outputs 'validated', 'encrypted'

        body.provider = body.provider || 'local';
        body.role = body.role || 'user';
        body.step3 = 'completed';

        return body; // ✅ Final completed body
    }
}
```

**Execution order:**

```bash
POST /users
{
  "name": "John Doe",
  "email": "john@example.com",
  "password": "mypassword"
}

# Console output:
# 1️⃣ Validating data...
# 2️⃣ Hashing password...
# Previous step result: validated
# 3️⃣ Setting defaults...
# Previous steps results: validated encrypted

# Final saved data:
{
  "name": "John Doe",
  "email": "john@example.com",
  "password": "$2b$10$...", // ✅ Encrypted
  "provider": "local",      // ✅ Default set
  "role": "user",          // ✅ Default set
  "step1": "validated",    // ✅ Passed through chain
  "step2": "encrypted",    // ✅ Passed through chain
  "step3": "completed"     // ✅ Final processing
}
```

##### ⚡ Simple Test Example

```typescript
// Minimal example for simple testing
@Crud({
    entity: User,
    allowedParams: ['name', 'email', 'password'],
})
@Controller('users')
export class UserController {
    constructor(public readonly crudService: UserService) {}

    @BeforeCreate()
    async step1(body: any, context: any) {
        body.step1 = 'first';
        console.log('Step 1:', body);
        return body;
    }

    @BeforeCreate()
    async step2(body: any, context: any) {
        body.step2 = 'second';
        console.log('Step 2:', body); // Check if step1 exists
        return body;
    }
}

// POST /users { "name": "test" }
// Console output:
// Step 1: { name: "test", step1: "first" }
// Step 2: { name: "test", step1: "first", step2: "second" }
```

##### Advantages

1.  **🎯 Intuitive**: Clear role from method names
2.  **🧹 Clean Code**: Routes configuration not complex
3.  **🔗 Chain Execution**: Multiple hooks execute sequentially with automatic data passing
4.  **🔄 Reusability**: Common hooks can be implemented through inheritance
5.  **🛡️ Type Safety**: TypeScript type checking support
6.  **✨ IntelliSense**: IDE auto-completion support
7.  **🚀 Entity-based UPDATE/DESTROY/RECOVER**: Full entity access with ID for advanced operations

#### 🛠️ Method 2: Routes Configuration Approach (Legacy)

#### Basic Usage

```typescript
@Controller('users')
@Crud({
    entity: User,
    routes: {
        create: {
            hooks: {
                assignBefore: async (body, context) => {
                    // Convert email to lowercase
                    if (body.email) {
                        body.email = body.email.toLowerCase();
                    }
                    return body;
                },

                assignAfter: async (entity, body, context) => {
                    // Set default role
                    if (!entity.role) {
                        entity.role = 'user';
                    }
                    return entity;
                },

                saveBefore: async (entity, context) => {
                    // Check duplicate email
                    const existing = await userService.findByEmail(entity.email);
                    if (existing) {
                        throw new Error('Email already exists');
                    }
                    return entity;
                },

                saveAfter: async (entity, context) => {
                    // Send welcome email
                    await emailService.sendWelcomeEmail(entity.email);
                    return entity;
                },
            },
        },

        update: {
            hooks: {
                assignBefore: async (entity, context) => {
                    // 🚀 NEW: Now receives full entity
                    // Auto-set update time
                    entity.updatedAt = new Date();

                    // Prevent modification of certain fields
                    const originalId = entity.id;
                    const originalCreatedAt = entity.createdAt;

                    // Restore protected fields if they were modified
                    entity.id = originalId;
                    entity.createdAt = originalCreatedAt;

                    return entity;
                },

                saveBefore: async (entity, context) => {
                    // Check permissions
                    const userId = context.request?.user?.id;
                    if (entity.id !== userId) {
                        throw new Error('Permission denied');
                    }
                    return entity;
                },
            },
        },

        destroy: {
            hooks: {
                destroyBefore: async (entity, context) => {
                    // 🆕 NEW! DESTROY before hook
                    console.log(`Preparing to delete user: ${entity.name} (ID: ${entity.id})`);

                    // Check delete permission
                    const userId = context.request?.user?.id;
                    if (entity.id !== userId && context.request?.user?.role !== 'admin') {
                        throw new Error('Permission denied: Cannot delete other users');
                    }

                    // Prevent deletion of locked accounts
                    if (entity.status === 'locked') {
                        throw new Error('Cannot delete locked user account');
                    }

                    // Set deletion metadata
                    entity.deletedBy = userId;
                    entity.deletedAt = new Date();

                    return entity;
                },

                destroyAfter: async (entity, context) => {
                    // 🆕 NEW! DESTROY after hook
                    console.log(`User deleted successfully: ${entity.name} (ID: ${entity.id})`);

                    // Cleanup related data
                    // await sessionService.revokeUserSessions(entity.id);
                    // await tokenService.revokeUserTokens(entity.id);

                    // Send deletion notification
                    // await emailService.notifyUserDeletion(entity);

                    // Log for audit
                    // await auditService.logUserDeletion(entity, context.request?.user);

                    return entity;
                },
            },
        },

        recover: {
            hooks: {
                recoverBefore: async (entity, context) => {
                    // 🆕 NEW! RECOVER before hook
                    console.log(`Preparing to recover user: ${entity.name} (ID: ${entity.id})`);

                    // Check recover permission
                    const userId = context.request?.user?.id;
                    if (entity.deletedBy !== userId && context.request?.user?.role !== 'admin') {
                        throw new Error('Permission denied: Cannot recover other users data');
                    }

                    // Prevent recovery of banned accounts
                    if (entity.status === 'banned') {
                        throw new Error('Cannot recover banned user account');
                    }

                    // Set recovery metadata
                    entity.recoveredBy = userId;
                    entity.recoveredAt = new Date();

                    return entity;
                },

                recoverAfter: async (entity, context) => {
                    // 🆕 NEW! RECOVER after hook
                    console.log(`User recovered successfully: ${entity.name} (ID: ${entity.id})`);

                    // Restore related data
                    // await sessionService.enableUserSessions(entity.id);
                    // await tokenService.reissueUserTokens(entity.id);

                    // Send recovery notification
                    // await emailService.notifyUserRecovery(entity);

                    // Log for audit
                    // await auditService.logUserRecovery(entity, context.request?.user);

                    return entity;
                },
            },
        },
    },
})
export class UserController {
    constructor(public readonly crudService: UserService) {}
}
```

#### Advanced Usage Example

```typescript
@Controller('posts')
@Crud({
    entity: Post,
    routes: {
        create: {
            hooks: {
                assignBefore: async (body, context) => {
                    // Auto-set user ID
                    const userId = context.request?.user?.id;
                    if (userId) {
                        body.userId = userId;
                    }

                    // Auto-generate slug
                    if (body.title && !body.slug) {
                        body.slug = slugify(body.title);
                    }

                    return body;
                },

                assignAfter: async (entity, body, context) => {
                    // Set default post status
                    if (!entity.status) {
                        entity.status = 'draft';
                    }

                    // Set publication date when publishing
                    if (entity.status === 'published' && !entity.publishedAt) {
                        entity.publishedAt = new Date();
                    }

                    return entity;
                },

                saveBefore: async (entity, context) => {
                    // Validate required fields
                    if (!entity.title?.trim()) {
                        throw new Error('Title is required');
                    }

                    // Check and resolve slug duplication
                    const existingPost = await postService.findBySlug(entity.slug);
                    if (existingPost) {
                        entity.slug = `${entity.slug}-${Date.now()}`;
                    }

                    return entity;
                },

                saveAfter: async (entity, context) => {
                    // Update search index
                    await searchService.indexPost(entity);

                    // Process tags
                    if (entity.tags?.length) {
                        await tagService.processPostTags(entity.id, entity.tags);
                    }

                    // Notify about published post
                    if (entity.status === 'published') {
                        await notificationService.notifyNewPost(entity);
                    }

                    return entity;
                },
            },
        },

        upsert: {
            hooks: {
                assignBefore: async (body, context) => {
                    const now = new Date();
                    body.updatedAt = now;

                    // Set creation date only for new data
                    if (!context.currentEntity) {
                        body.createdAt = now;
                    }

                    return body;
                },

                saveAfter: async (entity, context) => {
                    // Differentiate between newly created and updated cases
                    const isNew = !context.currentEntity;

                    if (isNew) {
                        await analyticsService.trackPostCreated(entity);
                    } else {
                        await analyticsService.trackPostUpdated(entity);
                    }

                    return entity;
                },
            },
        },
    },
})
export class PostController {
    constructor(public readonly crudService: PostService) {}
}
```

#### HookContext Usage

```typescript
// HookContext provides the following information
interface HookContext<T> {
    operation: 'create' | 'update' | 'upsert' | 'destroy' | 'recover' | 'show'; // Operation type
    params?: Record<string, any>; // URL parameters
    currentEntity?: T; // Current entity (update, upsert, destroy, recover)
    request?: any; // Express Request object
}

// Context usage example
const hooks = {
    assignBefore: async (body, context) => {
        console.log(`Operation type: ${context.operation}`);

        // Use requester information
        if (context.request?.user) {
            body.lastModifiedBy = context.request.user.id;
        }

        // Use URL parameters
        if (context.params?.parentId) {
            body.parentId = context.params.parentId;
        }

        // Use existing entity information (update, upsert only)
        if (context.currentEntity) {
            console.log('Existing data:', context.currentEntity);
        }

        return body;
    },
};
```

#### Show Operation Hooks (NEW! 🆕)

The `show` operation now supports `assignBefore` and `assignAfter` hooks for read-time data processing:

- **`assignBefore`**: Process query parameters before entity lookup (security filtering, parameter transformation)
- **`assignAfter`**: Process retrieved entity before returning it (data masking, calculated fields)

##### Show Operation Hook Examples

```typescript
@Controller('users')
@Crud({
    entity: User,
    routes: {
        show: {
            hooks: {
                // Process parameters before entity lookup
                assignBefore: async (params, context) => {
                    // Security: Validate user has permission to view this ID
                    const requesterId = context.request?.user?.id;
                    const targetId = params.id;
                    
                    if (requesterId !== targetId && context.request?.user?.role !== 'admin') {
                        throw new ForbiddenException('Cannot view other users');
                    }
                    
                    // Parameter transformation
                    if (typeof params.id === 'string') {
                        params.id = parseInt(params.id, 10);
                    }
                    
                    return params;
                },
                
                // Process entity after retrieval
                assignAfter: async (entity, _, context) => {
                    // Mask sensitive information
                    if (entity.email) {
                        entity.email = entity.email.replace(/(.{2}).*(@.*)/, '$1***$2');
                    }
                    
                    // Hide sensitive fields based on viewer's role
                    if (context.request?.user?.role !== 'admin') {
                        delete entity.salary;
                        delete entity.ssn;
                    }
                    
                    // Add calculated fields
                    (entity as any).displayName = `${entity.firstName} ${entity.lastName}`;
                    (entity as any).age = calculateAge(entity.birthDate);
                    
                    // Track view statistics (without saving to DB)
                    await analyticsService.trackView(entity.id, context.request?.user?.id);
                    
                    return entity;
                }
            }
        }
    }
})
export class UserController {
    constructor(public readonly crudService: UserService) {}
}
```

##### Common Use Cases for Show Hooks

1. **Data Masking & Security**:
```typescript
assignAfter: async (entity, _, context) => {
    // Mask PII based on viewer permissions
    const isOwner = entity.id === context.request?.user?.id;
    const isAdmin = context.request?.user?.role === 'admin';
    
    if (!isOwner && !isAdmin) {
        entity.phone = entity.phone?.replace(/\d(?=\d{4})/g, '*');
        entity.email = entity.email?.replace(/(.{2}).*(@.*)/, '$1***$2');
        delete entity.dateOfBirth;
    }
    
    return entity;
}
```

2. **Enriching Response with Calculated Data**:
```typescript
assignAfter: async (entity, _, context) => {
    // Add computed fields without modifying database
    (entity as any).fullAddress = `${entity.street}, ${entity.city}, ${entity.country}`;
    (entity as any).accountAge = daysSince(entity.createdAt);
    (entity as any).isPremium = entity.subscriptionLevel === 'premium';
    
    // Fetch additional data from external services
    (entity as any).creditScore = await creditService.getScore(entity.id);
    
    return entity;
}
```

3. **Parameter Validation & Transformation**:
```typescript
assignBefore: async (params, context) => {
    // Validate and transform ID formats
    if (params.uuid && !isValidUUID(params.uuid)) {
        throw new BadRequestException('Invalid UUID format');
    }
    
    // Apply tenant isolation
    if (context.request?.tenant) {
        params.tenantId = context.request.tenant.id;
    }
    
    return params;
}
```

4. **Analytics & Monitoring**:
```typescript
assignAfter: async (entity, _, context) => {
    // Track access patterns without affecting response
    await Promise.all([
        auditLog.record({
            action: 'VIEW',
            entityType: 'User',
            entityId: entity.id,
            viewerId: context.request?.user?.id,
            timestamp: new Date()
        }),
        metricsService.increment('user.profile.views'),
        cacheService.warm(entity.id, entity) // Pre-cache for future requests
    ]);
    
    return entity;
}
```

#### Reusing Common Hook Functions

```typescript
// Define