> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/charlietyn/rest-generic-class/llms.txt
> Use this file to discover all available pages before exploring further.

# Bulk Updates and Operations

> Optimize multiple record operations with bulk endpoints and transaction handling

Bulk operations allow you to create, update, or delete multiple records in a single request. Rest Generic Class provides built-in bulk support with automatic transaction handling and rollback on errors.

## Why Bulk Operations?

**Without bulk operations:**

```javascript theme={null}
// 100 separate requests
for (const product of products) {
  await fetch(`/api/v1/products/${product.id}`, {
    method: 'PUT',
    body: JSON.stringify({ stock: product.stock })
  });
}
// Total time: ~5 seconds
// Network overhead: 100 round trips
```

**With bulk operations:**

```javascript theme={null}
// 1 request
await fetch('/api/v1/products/update-multiple', {
  method: 'POST',
  body: JSON.stringify({
    product: products.map(p => ({ id: p.id, stock: p.stock }))
  })
});
// Total time: ~200ms
// Network overhead: 1 round trip
```

**Performance gain:** **25x faster** ✅

## Bulk Create

### Setup

Define the `MODEL` constant in your model:

```php Product.php theme={null}
class Product extends BaseModel
{
    const MODEL = 'product'; // Must be lowercase
    
    protected $fillable = ['name', 'price', 'stock', 'category_id'];
}
```

### Single Create (Normal)

```http theme={null}
POST /api/v1/products
Content-Type: application/json

{
  "name": "Wireless Mouse",
  "price": 29.99,
  "stock": 150,
  "category_id": 3
}
```

Response:

```json theme={null}
{
  "success": true,
  "model": {
    "id": 10,
    "name": "Wireless Mouse",
    "price": 29.99,
    "stock": 150,
    "category_id": 3
  }
}
```

### Bulk Create

Wrap records in an array with the model key:

```http theme={null}
POST /api/v1/products
Content-Type: application/json

{
  "product": [
    {
      "name": "Wireless Mouse",
      "price": 29.99,
      "stock": 150,
      "category_id": 3
    },
    {
      "name": "USB-C Cable",
      "price": 12.99,
      "stock": 500,
      "category_id": 4
    },
    {
      "name": "Laptop Stand",
      "price": 39.99,
      "stock": 75,
      "category_id": 3
    }
  ]
}
```

Response:

```json theme={null}
{
  "success": true,
  "0": {
    "success": true,
    "model": {
      "id": 10,
      "name": "Wireless Mouse",
      ...
    }
  },
  "1": {
    "success": true,
    "model": {
      "id": 11,
      "name": "USB-C Cable",
      ...
    }
  },
  "2": {
    "success": true,
    "model": {
      "id": 12,
      "name": "Laptop Stand",
      ...
    }
  }
}
```

<Note>
  The key must match the lowercase `MODEL` constant. For `Product::MODEL = 'product'`, use `{"product": [...]}`.
</Note>

## Bulk Update

### Route Setup

Register the `updateMultiple` route:

```php routes/api.php theme={null}
Route::post('products/update-multiple', [ProductController::class, 'updateMultiple']);
```

### Request Format

Each record must include the primary key:

```http theme={null}
POST /api/v1/products/update-multiple
Content-Type: application/json

{
  "product": [
    {"id": 10, "stock": 140},
    {"id": 11, "stock": 495},
    {"id": 12, "price": 34.99, "stock": 70}
  ]
}
```

Response:

```json theme={null}
{
  "success": true,
  "models": [
    {
      "success": true,
      "model": {
        "id": 10,
        "name": "Wireless Mouse",
        "stock": 140,
        "updated_at": "2026-03-05T12:00:00.000000Z"
      }
    },
    {
      "success": true,
      "model": {
        "id": 11,
        "name": "USB-C Cable",
        "stock": 495,
        "updated_at": "2026-03-05T12:00:00.000000Z"
      }
    },
    {
      "success": true,
      "model": {
        "id": 12,
        "name": "Laptop Stand",
        "price": 34.99,
        "stock": 70,
        "updated_at": "2026-03-05T12:00:00.000000Z"
      }
    }
  ]
}
```

### Partial Updates

You don't need to send all fields, only the ones you want to update:

```json theme={null}
{
  "product": [
    {"id": 10, "stock": 140},
    {"id": 11, "stock": 495}
  ]
}
```

## Transaction Handling

All bulk operations are wrapped in database transactions.

### Automatic Rollback

If **any** record fails, the **entire transaction** rolls back:

```http theme={null}
POST /api/v1/products/update-multiple
Content-Type: application/json

{
  "product": [
    {"id": 10, "stock": 140},
    {"id": 999, "stock": 0},  // ID doesn't exist
    {"id": 12, "stock": 70}
  ]
}
```

Response (422 Unprocessable Entity):

```json theme={null}
{
  "success": false,
  "models": [
    {
      "success": false,
      "errors": {
        "id": ["Product with ID 999 not found"]
      }
    }
  ]
}
```

**Result:** ❌ Products 10 and 12 are **NOT updated** (transaction rolled back)

<Warning>
  Bulk operations are all-or-nothing. One failure cancels the entire batch.
</Warning>

### Commit on Success

If all records succeed, the transaction commits:

```php theme={null}
// In RestController::updateMultiple()
DB::beginTransaction();
try {
    $result = $this->service->update_multiple($params);
    if ($result['success'])
        DB::commit();
} catch (\Throwable $e) {
    DB::rollBack();
    throw $e;
}
```

## Validation in Bulk Operations

### Per-Record Validation

Each record is validated individually:

```http theme={null}
POST /api/v1/products
Content-Type: application/json

{
  "product": [
    {"name": "Mouse", "price": 29.99, "stock": 150, "category_id": 3},
    {"name": "Cable", "price": -5.00, "stock": 500, "category_id": 4},  // Invalid price
    {"name": "Stand", "price": 39.99, "stock": 75, "category_id": 3}
  ]
}
```

Response:

```json theme={null}
{
  "success": false,
  "1": {
    "success": false,
    "errors": {
      "price": ["The price must be greater than 0"]
    }
  }
}
```

### Validation Rules

Define validation in your model or request:

```php ProductRequest.php theme={null}
public function rules(): array
{
    return [
        'name' => 'required|string|max:255',
        'price' => 'required|numeric|min:0',
        'stock' => 'required|integer|min:0',
        'category_id' => 'required|exists:categories,id',
    ];
}
```

These rules apply to **every record** in a bulk operation.

## Cache Invalidation

Bulk operations invalidate cache for the entire model.

### How It Works

```php theme={null}
// Before bulk update
Product cache version: 5

// Bulk update
POST /api/v1/products/update-multiple
{
  "product": [{"id": 10, "stock": 140}, {"id": 11, "stock": 495}]
}

// After bulk update
Product cache version: 6 (bumped once for entire batch)
```

**Efficiency:** Cache version is bumped **once** per batch, not once per record.

<Note>
  All cached `list_all` and `get_one` queries for products are invalidated with a single version bump.
</Note>

## Performance Optimization

### Benchmark: 100 Record Updates

| Method         | Requests | Time  | Database Queries      |
| -------------- | -------- | ----- | --------------------- |
| Individual PUT | 100      | 5.2s  | 200+ (2 per record)   |
| Bulk Update    | 1        | 0.18s | 101 (1 + 100 updates) |

**Improvement:** **28.8x faster** ✅

### Best Practices

✅ **Do:**

* Use bulk operations for batches of 10+ records
* Send only fields that need updating
* Batch similar operations together
* Use pagination for very large batches (1000+)

❌ **Don't:**

* Use bulk operations for 1-2 records (overhead not worth it)
* Send all fields when only one changed
* Mix creates and updates in same batch
* Send batches larger than 500 records without testing

## Real-World Examples

### Inventory Update (E-commerce)

Update stock levels from warehouse system:

```http theme={null}
POST /api/v1/products/update-multiple
Content-Type: application/json

{
  "product": [
    {"id": 1001, "stock": 50},
    {"id": 1002, "stock": 0},
    {"id": 1003, "stock": 150},
    {"id": 1004, "stock": 75},
    {"id": 1005, "stock": 200}
  ]
}
```

### Price Update (Admin Dashboard)

Apply discount to multiple products:

```javascript theme={null}
// Calculate discounted prices
const updates = selectedProducts.map(product => ({
  id: product.id,
  discount_price: product.price * 0.8,  // 20% off
  discount_expires_at: '2026-04-01'
}));

// Send bulk update
await fetch('/api/v1/products/update-multiple', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({ product: updates })
});
```

### Status Batch Update (CRM)

Update lead status after import:

```http theme={null}
POST /api/v1/leads/update-multiple
Content-Type: application/json

{
  "lead": [
    {"id": 501, "status": "qualified", "assigned_to": 10},
    {"id": 502, "status": "qualified", "assigned_to": 10},
    {"id": 503, "status": "qualified", "assigned_to": 11},
    {"id": 504, "status": "qualified", "assigned_to": 11}
  ]
}
```

### Bulk Create from Import (CSV Upload)

```javascript theme={null}
// Parse CSV rows
const products = csvRows.map(row => ({
  name: row.name,
  price: parseFloat(row.price),
  stock: parseInt(row.stock),
  category_id: categoryMap[row.category]
}));

// Chunk into batches of 100
for (let i = 0; i < products.length; i += 100) {
  const batch = products.slice(i, i + 100);
  await fetch('/api/v1/products', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({ product: batch })
  });
}
```

## Error Handling

### Partial Success Not Supported

Rest Generic Class does not support partial success in bulk operations. Either all records succeed or all fail.

**Why?**\
Partial success leads to inconsistent state and makes error recovery complex.

**Alternative:**\
If you need partial success, send smaller batches:

```javascript theme={null}
// Instead of 1 batch of 100 (all-or-nothing)
await bulkUpdate(products); // Fails if any record fails

// Send 10 batches of 10 (isolated failures)
for (const batch of chunks(products, 10)) {
  try {
    await bulkUpdate(batch);
  } catch (error) {
    console.error('Batch failed:', batch, error);
    // Continue with next batch
  }
}
```

### Logging Errors

Bulk operation errors are logged automatically:

```php theme={null}
// In RestController::updateMultiple()
Log::channel('rest-generic-class')->error('Update multiple failed', [
    'controller' => static::class,
    'method' => __FUNCTION__,
    'exception' => $e,
]);
```

Check logs at `storage/logs/rest-generic-class.log`.

## Testing Bulk Operations

### Unit Test Example

```php theme={null}
public function test_bulk_update_products()
{
    $products = Product::factory()->count(3)->create();

    $response = $this->postJson('/api/v1/products/update-multiple', [
        'product' => [
            ['id' => $products[0]->id, 'stock' => 100],
            ['id' => $products[1]->id, 'stock' => 200],
            ['id' => $products[2]->id, 'stock' => 300],
        ]
    ]);

    $response->assertStatus(200)
             ->assertJsonPath('success', true)
             ->assertJsonPath('models.0.success', true)
             ->assertJsonPath('models.1.success', true)
             ->assertJsonPath('models.2.success', true);

    $this->assertDatabaseHas('products', ['id' => $products[0]->id, 'stock' => 100]);
    $this->assertDatabaseHas('products', ['id' => $products[1]->id, 'stock' => 200]);
    $this->assertDatabaseHas('products', ['id' => $products[2]->id, 'stock' => 300]);
}

public function test_bulk_update_rollback_on_error()
{
    $product = Product::factory()->create(['stock' => 50]);

    $response = $this->postJson('/api/v1/products/update-multiple', [
        'product' => [
            ['id' => $product->id, 'stock' => 100],
            ['id' => 99999, 'stock' => 200], // Non-existent ID
        ]
    ]);

    $response->assertStatus(422);

    // Original stock should be unchanged (rollback)
    $this->assertDatabaseHas('products', ['id' => $product->id, 'stock' => 50]);
}
```

## Troubleshooting

### "MODEL constant not defined" Error

**Symptom:** Bulk create fails with "MODEL not found"

**Cause:** Missing `MODEL` constant in model

**Solution:**

```php theme={null}
const MODEL = 'product'; // Add this to your model
```

### Wrong Key in Request

**Symptom:** Returns single-record response instead of bulk

**Cause:** Key doesn't match lowercase `MODEL` constant

**Fix:**

```json theme={null}
// Wrong
{"Product": [...]}  // Capital P
{"products": [...]} // Plural

// Correct
{"product": [...]}  // Matches MODEL = 'product'
```

### Transaction Timeout

**Symptom:** Request times out with large batches

**Cause:** Batch too large or slow validation

**Solution:**

```php theme={null}
// Increase timeout for bulk endpoints
set_time_limit(300); // 5 minutes

// Or split into smaller batches
$chunks = array_chunk($records, 100);
foreach ($chunks as $chunk) {
    $this->updateMultiple($chunk);
}
```

### Memory Limit Exceeded

**Symptom:** "Allowed memory size exhausted"

**Cause:** Loading too many models into memory

**Solution:**

```php theme={null}
// Increase memory limit
ini_set('memory_limit', '256M');

// Or process in smaller batches
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Caching" icon="bolt" href="/guides/caching">
    Understand cache invalidation with bulk operations
  </Card>

  <Card title="Many-to-Many" icon="diagram-project" href="/guides/many-to-many">
    Bulk attach/detach operations for pivot tables
  </Card>

  <Card title="Performance" icon="gauge-high" href="/performance/optimization">
    Advanced performance optimization techniques
  </Card>

  <Card title="Testing" icon="vial" href="/testing/integration">
    Write integration tests for bulk endpoints
  </Card>
</CardGroup>

## Evidence

* **File:** `src/Core/Services/BaseService.php`\
  **Lines:** 611-627, 664-676\
  Implements `create()` (detects bulk), `save_array()`, and `update_multiple()`

* **File:** `src/Core/Controllers/RestController.php`\
  **Lines:** 158-176, 211-230\
  Shows `store()` and `updateMultiple()` with transaction handling

* **File:** `src/Core/Models/BaseModel.php`\
  **Lines:** 28\
  Defines `MODEL` constant requirement for bulk operations

* **File:** `README.md`\
  **Lines:** 181-194\
  Shows bulk update example usage
