Laravel Job Batching, Chaining &amp; Rate Limiting | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues        On this page       1. [  Why Basic dispatch() Is Not Enough ](#why-basic-codedispatchcode-is-not-enough)
2. [  Job Batching with Bus::batch() ](#job-batching-with-codebusbatchcode)
3. [  Adding Jobs to a Running Batch ](#adding-jobs-to-a-running-batch)
4. [  Job Chaining with Bus::chain() ](#job-chaining-with-codebuschaincode)
5. [  Rate-Limited Job Middleware ](#rate-limited-job-middleware)
6. [  Custom Backoff on Rate Limit ](#custom-backoff-on-rate-limit)
7. [  Combining All Three ](#combining-all-three)
8. [  Key Takeaways ](#key-takeaways)

  ![Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues](https://cdn.msaied.com/515/f0ef4270f8cb79ceff107c7a7c63f1ed.png)

  #laravel   #queues   #jobs   #horizon   #async  

 Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues 
=======================================================================

     6 Aug 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said  

       Table of contents

1. [  01   Why Basic dispatch() Is Not Enough  ](#why-basic-codedispatchcode-is-not-enough)
2. [  02   Job Batching with Bus::batch()  ](#job-batching-with-codebusbatchcode)
3. [  03   Adding Jobs to a Running Batch  ](#adding-jobs-to-a-running-batch)
4. [  04   Job Chaining with Bus::chain()  ](#job-chaining-with-codebuschaincode)
5. [  05   Rate-Limited Job Middleware  ](#rate-limited-job-middleware)
6. [  06   Custom Backoff on Rate Limit  ](#custom-backoff-on-rate-limit)
7. [  07   Combining All Three  ](#combining-all-three)
8. [  08   Key Takeaways  ](#key-takeaways)

 Why Basic `dispatch()` Is Not Enough
------------------------------------

Single-job dispatching works fine for isolated tasks, but real SaaS workloads demand coordination: import a CSV, notify each row's owner, then send a summary email. Get any step wrong and you want partial retries — not a full restart. Laravel's batch and chain APIs, combined with rate-limited job middleware, give you that control.

---

Job Batching with `Bus::batch()`
--------------------------------

Batches let you dispatch a collection of jobs and react when the whole set finishes, partially fails, or is cancelled.

```php
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;

$batch = Bus::batch([
    new ProcessRowJob($row) for $row in $rows, // spread or array
])
->then(fn (Batch $batch) => SummaryMail::dispatch($batch->id))
->catch(fn (Batch $batch, Throwable $e) => Log::error('Batch failed', [
    'batch' => $batch->id,
    'error' => $e->getMessage(),
]))
->finally(fn (Batch $batch) => BatchCompleted::dispatch($batch->id))
->name('csv-import')
->allowFailures()   // keep running even if some jobs fail
->dispatch();

```

> `allowFailures()` is critical for large imports: one bad row should not cancel 10,000 others.

Track progress in Filament or a dashboard via `$batch->progress()`, `$batch->failedJobs`, and `$batch->pendingJobs`.

### Adding Jobs to a Running Batch

Inside a batched job you can append more work — useful for tree-shaped workloads:

```php
public function handle(): void
{
    $this->batch()->add([
        new ProcessChildJob($this->parentId, $child)
        foreach ($this->children() as $child),
    ]);
}

```

---

Job Chaining with `Bus::chain()`
--------------------------------

Chains enforce strict sequential execution. If any job fails, the rest are abandoned.

```php
Bus::chain([
    new VerifyPayment($orderId),
    new FulfillOrder($orderId),
    new SendConfirmationEmail($orderId),
])
->catch(fn (Throwable $e) => Order::fail($orderId, $e->getMessage()))
->dispatch();

```

You can mix batches inside chains for fan-out/fan-in patterns:

```php
Bus::chain([
    new PrepareImport($fileId),
    Bus::batch($rowJobs)->allowFailures(),
    new FinaliseImport($fileId),
])->dispatch();

```

This runs `PrepareImport`, then all row jobs in parallel, then `FinaliseImport` — a powerful pattern for ETL pipelines.

---

Rate-Limited Job Middleware
---------------------------

Throttling at the job level prevents hammering third-party APIs regardless of how many workers you run.

```php
use Illuminate\Queue\Middleware\RateLimited;
use Illuminate\Support\Facades\RateLimiter;

// AppServiceProvider::boot()
RateLimiter::for('stripe', fn () =>
    Limit::perMinute(100)->by('stripe-api')
);

// Inside the job
public function middleware(): array
{
    return [new RateLimited('stripe')];
}

```

When the limit is hit, the job is **automatically released back** to the queue with an exponential backoff — no manual `$this->release()` needed.

### Custom Backoff on Rate Limit

```php
use Illuminate\Queue\Middleware\RateLimitedWithRedis;

public function middleware(): array
{
    return [(new RateLimitedWithRedis('stripe'))->dontRelease()];
    // dontRelease() deletes the job instead of re-queuing — use carefully
}

```

`RateLimitedWithRedis` uses atomic Lua scripts for precise per-second limits, making it safer under Horizon's multi-worker concurrency.

---

Combining All Three
-------------------

A production import pipeline might look like:

```php
Bus::chain([
    new ValidateFile($fileId),                        // sequential
    Bus::batch($parseJobs)->allowFailures(),           // parallel parse
    Bus::batch($enrichJobs)->allowFailures(),          // parallel API calls (rate-limited)
    new GenerateReport($fileId),                      // sequential
])->dispatch();

```

Each `enrichJob` carries `RateLimited('external-api')` middleware, so the batch fans out as fast as the limiter allows without a single line of throttle logic in the business code.

---

Key Takeaways
-------------

- Use **`Bus::batch()`** for parallel fan-out; use **`allowFailures()`** for fault-tolerant imports.
- Use **`Bus::chain()`** for strict sequential steps; nest batches inside chains for fan-out/fan-in.
- Attach **`RateLimited`** middleware at the job level — it survives worker restarts and scales across all Horizon processes.
- Prefer **`RateLimitedWithRedis`** over the plain variant when you need sub-second precision.
- Track batch state via `$batch->progress()` for real-time dashboards without polling your database directly.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fjob-batching-chaining-and-rate-limited-middleware-in-laravel-queues-4&text=Job+Batching%2C+Chaining%2C+and+Rate-Limited+Middleware+in+Laravel+Queues) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fjob-batching-chaining-and-rate-limited-middleware-in-laravel-queues-4) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  What happens to a batch when one job throws an exception and `allowFailures()` is set?        The failed job is recorded in `job_batches.failed_jobs` and the `catch` callback fires, but the remaining pending jobs continue processing. The batch only moves to `finally` once all jobs have either completed or failed. 

      Q02  Can I use `RateLimited` middleware with batched jobs?        Yes. Each job in a batch is an independent queue message, so middleware is applied per-job. Rate-limited jobs are released back to the queue and retried, which may slow overall batch completion but will not cancel the batch. 

      Q03  How do I prevent a chain from silently swallowing failures?        Always attach a `-&gt;catch()` callback to `Bus::chain()`. Without it, a failed job abandons the rest of the chain with no notification. The callback receives the `Throwable` so you can alert, compensate, or update domain state. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://www.msaied.com/articles) 

 [ ![Livewire v4.4.6 Released: Bug Fixes, Test Improvements, and Alpine v3.17.4](https://cdn.msaied.com/689/454c52282f3ef5d585905e5952ca969c.png) Livewire Laravel Alpine.js 

### Livewire v4.4.6 Released: Bug Fixes, Test Improvements, and Alpine v3.17.4

Livewire v4.4.6 ships with 18 changes including validation performance improvements, better test assertions, k...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 21 Sep 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/livewire-v446-released-bug-fixes-test-improvements-and-alpine-v3174) [ ![Laravel 14: New Features, Breaking Changes, and PHP 8.4 Requirement](https://cdn.msaied.com/688/2dfe8f11b0bef35c0ee6db912004209f.png) Laravel 14 PHP 8.4 Breaking Changes 

### Laravel 14: New Features, Breaking Changes, and PHP 8.4 Requirement

Laravel 14 is expected in Q1 2027 and will require PHP 8.4. Here is everything known so far from the master br...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 21 Sep 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-14-new-features-breaking-changes-and-php-84-requirement) [ ![Building a Laravel Package: Service Providers, Auto-Discovery, and Config Merging](https://cdn.msaied.com/687/e53f819c8f897c1ad12a1df0661a18f7.png) laravel packages service-providers 

### Building a Laravel Package: Service Providers, Auto-Discovery, and Config Merging

Learn how to build a production-ready Laravel package from scratch — covering service provider design, auto-di...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 21 Sep 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/building-a-laravel-package-service-providers-auto-discovery-and-config-merging-4) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
