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

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

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

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Job Batching, Chaining, and Rate-Limited Middleware for Laravel Queues        On this page       1. [  Job Batching, Chaining, and Rate-Limited Middleware ](#job-batching-chaining-and-rate-limited-middleware)
2. [  Batching: Fan-Out With a Finish Line ](#batching-fan-out-with-a-finish-line)
3. [  Adding Jobs to a Running Batch ](#adding-jobs-to-a-running-batch)
4. [  Chaining: Sequential Pipelines With Error Isolation ](#chaining-sequential-pipelines-with-error-isolation)
5. [  Rate-Limited Job Middleware ](#rate-limited-job-middleware)
6. [  Takeaways ](#takeaways)

  ![Job Batching, Chaining, and Rate-Limited Middleware for Laravel Queues](https://cdn.msaied.com/414/154bd945ee1677f140cd1ea4132e5b66.png)

  #laravel   #queues   #jobs   #async  

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

     12 Jul 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Job Batching, Chaining, and Rate-Limited Middleware  ](#job-batching-chaining-and-rate-limited-middleware)
2. [  02   Batching: Fan-Out With a Finish Line  ](#batching-fan-out-with-a-finish-line)
3. [  03   Adding Jobs to a Running Batch  ](#adding-jobs-to-a-running-batch)
4. [  04   Chaining: Sequential Pipelines With Error Isolation  ](#chaining-sequential-pipelines-with-error-isolation)
5. [  05   Rate-Limited Job Middleware  ](#rate-limited-job-middleware)
6. [  06   Takeaways  ](#takeaways)

 Job Batching, Chaining, and Rate-Limited Middleware
---------------------------------------------------

Laravel's queue system is deceptively powerful once you move past `dispatch()`. Three features — batching, chaining, and rate-limited middleware — compose into workflows that are resilient, observable, and polite to external APIs. This article shows how to wire them together correctly.

---

Batching: Fan-Out With a Finish Line
------------------------------------

A batch dispatches many jobs in parallel and lets you react when they all finish (or any one fails).

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

$batch = Bus::batch([
    new ProcessInvoice($invoice1),
    new ProcessInvoice($invoice2),
    new ProcessInvoice($invoice3),
])
->then(fn (Batch $batch) => Report::markComplete($batch->id))
->catch(fn (Batch $batch, Throwable $e) => Report::markFailed($batch->id, $e->getMessage()))
->finally(fn (Batch $batch) => Cache::forget("batch:{$batch->id}"))
->onQueue('invoices')
->dispatch();

session(['batch_id' => $batch->id]);

```

The `then` callback fires only when **all** jobs succeed. `catch` fires on the **first** failure but the batch continues processing remaining jobs by default. Call `$batch->cancel()` inside `catch` to halt everything.

### Adding Jobs to a Running Batch

Jobs can push siblings into their own batch — useful for recursive fan-out:

```php
public function handle(): void
{
    $children = $this->fetchChildIds();

    if ($children->isNotEmpty()) {
        $this->batch()->add(
            $children->map(fn ($id) => new ProcessChild($id))->all()
        );
    }
}

```

This keeps the batch open until every dynamically added job also completes.

---

Chaining: Sequential Pipelines With Error Isolation
---------------------------------------------------

Chaining runs jobs one after another, stopping on failure.

```php
Bus::chain([
    new ValidateOrder($order),
    new ChargePayment($order),
    new SendConfirmationEmail($order),
])
->catch(function (Throwable $e) use ($order) {
    $order->markFailed($e->getMessage());
    Notification::send($order->owner, new OrderFailedNotification($order));
})
->dispatch();

```

> **Gotcha:** the `catch` closure is serialized. Avoid injecting large objects — use IDs and re-query inside the closure.

You can mix batches inside chains for hybrid workflows:

```php
Bus::chain([
    new PrepareExport($report),
    Bus::batch([
        new ExportChunk($report, 0),
        new ExportChunk($report, 1),
        new ExportChunk($report, 2),
    ]),
    new FinalizeExport($report),
])->dispatch();

```

The chain pauses at the batch step until all chunks complete, then continues to `FinalizeExport`.

---

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

When jobs call external APIs you need to throttle throughput without blocking workers. Job middleware is the right tool — not `sleep()`.

```php
namespace App\Jobs\Middleware;

use Illuminate\Support\Facades\RateLimiter;

class ThrottleStripeApi
{
    public function handle(object $job, callable $next): void
    {
        RateLimiter::attempt(
            key: 'stripe-api',
            maxAttempts: 100,
            callback: fn () => $next($job),
            decaySeconds: 60,
        ) || $job->release(10); // re-queue after 10 s if limit hit
    }
}

```

Attach it to any job:

```php
public function middleware(): array
{
    return [new ThrottleStripeApi()];
}

```

Laravel ships `Illuminate\Queue\Middleware\RateLimited` for Redis-backed limiting with the named limiter API:

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

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

```

Define the limiter in `AppServiceProvider`:

```php
RateLimiter::for('stripe', fn () =>
    Limit::perMinute(100)->by('stripe-global')
);

```

The middleware automatically releases the job back to the queue with a calculated delay, so workers stay busy processing other jobs instead of sleeping.

---

Takeaways
---------

- Use **batches** for parallel fan-out; use `then`/`catch`/`finally` for lifecycle hooks.
- Use **chains** for sequential workflows; keep `catch` closures lightweight and ID-based.
- Embed a batch inside a chain to get parallel middle steps with sequential bookends.
- Use **rate-limited middleware** — not `sleep()` — to throttle external API calls; workers stay productive.
- Named `RateLimiter` definitions keep throttle logic centralized and testable.
- Always test batch/chain behavior with `Queue::fake()` and assert on `Bus::assertBatched()` / `Bus::assertChained()`.

 Found this useful?

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

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

  3 questions  

     Q01  Can a batched job add more jobs to the same batch at runtime?        Yes. Inside a job's handle method, call `$this-&gt;batch()-&gt;add([...])` to push sibling jobs into the running batch. The batch stays open until all dynamically added jobs also complete. 

      Q02  What happens to remaining batch jobs when one fails?        By default the batch continues processing remaining jobs and the `catch` callback fires once. Call `$batch-&gt;cancel()` inside `catch` if you want to stop all pending jobs immediately. 

      Q03  Why use rate-limited middleware instead of sleeping inside the job?        Sleeping blocks the worker process for the entire sleep duration, wasting capacity. Rate-limited middleware releases the job back to the queue with a delay, freeing the worker to process other jobs in the meantime. 

  Continue reading

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

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

 [ ![Laravel Broadcasting with Reverb: Private Channels, Presence, and Auth Guards](https://cdn.msaied.com/416/26c2793902d9db428140205417b2dfb4.png) laravel reverb broadcasting 

### Laravel Broadcasting with Reverb: Private Channels, Presence, and Auth Guards

Go beyond the basics of Laravel Reverb. Learn how to secure private and presence channels, wire up custom auth...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 12 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-broadcasting-with-reverb-private-channels-presence-and-auth-guards) [ ![Cursor Pagination, Chunked Iteration, and Lazy Collections at Scale in Laravel](https://cdn.msaied.com/415/eb2d62822119280148e092d25b36c6ae.png) laravel eloquent performance 

### Cursor Pagination, Chunked Iteration, and Lazy Collections at Scale in Laravel

Offset pagination breaks under large datasets. Learn how cursor pagination, chunked iteration, and lazy collec...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 12 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/cursor-pagination-chunked-iteration-and-lazy-collections-at-scale-in-laravel-2) [ ![Laravel Observers vs. Model Events: Choosing the Right Hook for Side Effects](https://cdn.msaied.com/413/ec9d48bb1167db4a2ec2e0784f3be002.png) laravel eloquent observers 

### Laravel Observers vs. Model Events: Choosing the Right Hook for Side Effects

Model events and observers both react to Eloquent lifecycle moments, but picking the wrong one creates hidden...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 11 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-observers-vs-model-events-choosing-the-right-hook-for-side-effects-1) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://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://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://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)
