Introduction to Jobs in Laravel

Laravel Jobs provide a powerful way to handle background processing, asynchronous tasks, and deferred execution. Instead of performing everything during a web request, you can offload long-running, expensive, or repetitive operations into jobs. Jobs are simple PHP classes that contain logic you want to run in the background, such as sending emails, processing video uploads, generating reports, clearing caches, or syncing data. Laravel’s job system is tightly integrated with its queue subsystem, giving developers an elegant API to dispatch, delay, retry, fail, and monitor background tasks.

In modern applications, performance and scalability are crucial. Running heavy tasks synchronously slows down response times and burdens servers. Jobs allow applications to remain fast and responsive while still performing complex operations behind the scenes. This article explores everything you need to know about Jobs in Laravel, including job creation, dispatching, queue workers, middleware, chaining, batching, retry logic, failed jobs, monitoring, queues, drivers, database interactions, events, and real-world examples.

What Are Jobs in Laravel

A Job in Laravel is a simple class that encapsulates a unit of work. When a job is dispatched, it is pushed into a queue to be processed later by a worker. This architecture allows you to run tasks asynchronously.

Jobs are ideal for:

  • Sending emails
  • Sending notifications
  • Processing uploaded files
  • Generating PDFs
  • Syncing with third-party APIs
  • Running scheduled tasks
  • Performing database cleanups
  • Performing computationally heavy tasks

Every job implements the handle() method which contains the business logic to execute when the worker processes the job.

How Jobs Work With Queues

Jobs are designed to work alongside Laravel’s queue system. The queue allows the framework to delay execution and process tasks in the background.

Workflow:

  1. You dispatch a job
  2. Laravel stores it in a queue
  3. A queue worker picks up the job
  4. Worker executes the handle() method
  5. Job completes or fails

The separation of responsibility ensures smoother performance and better scalability.


Creating a Job Using Artisan

To create a job class, run:

php artisan make:job SendEmailJob

This generates a file inside:

app/Jobs/SendEmailJob.php

Laravel automatically scaffolds the structure of the job.


Structure of a Job Class

A typical job class looks like:

class SendEmailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $user;
public function __construct(User $user)
{
    $this->user = $user;
}
public function handle()
{
    Mail::to($this->user->email)->send(new WelcomeMail());
}
}

Important components include:

  • ShouldQueue interface: tells Laravel to process asynchronously
  • Dispatchable trait: provides dispatch functionality
  • InteractsWithQueue trait: access queue-related methods
  • Queueable trait: configure queue settings
  • SerializesModels trait: serialize Eloquent models safely

Dispatching Jobs

Dispatching a job means pushing it onto the queue.

SendEmailJob::dispatch($user);

You can also use:

dispatch(new SendEmailJob($user));

Passing Data to Jobs

Jobs often require data. You pass them through constructors.

public function __construct(User $user, string $message)
{
$this->user = $user;
$this->message = $message;
}

Dispatch:

SendEmailJob::dispatch($user, 'Welcome to the platform');

Immediate vs Queued Jobs

If a job does not implement ShouldQueue, it runs immediately.

class SimpleJob
{
public function handle()
{
    // executes instantly
}
}

For background processing, implement:

implements ShouldQueue

Configuring Queues

Laravel supports multiple queue drivers:

  • database
  • redis
  • beanstalkd
  • sqs
  • sync (runs immediately)
  • null (discards jobs)

Configure in:

config/queue.php

Running Queue Workers

To process jobs:

php artisan queue:work

This starts a worker that listens for jobs.


The queue:listen Command

Alternative:

php artisan queue:listen

Listen is slower because it restarts on each job. queue:work is recommended for production.


Supervising Queue Workers

In production, use Supervisor to keep workers alive.

Example Supervisor configuration:

[program:laravel_queue]
process_name=%(program_name)s_%(process_num)02d
command=php artisan queue:work --sleep=3 --tries=3

Job Middleware

Laravel allows attaching middleware to jobs.

Example:

public function middleware()
{
return [
    new WithoutOverlapping($this->user->id),
];
}

Job middleware is similar to HTTP middleware.


Rate Limiting Jobs Using Middleware

public function middleware()
{
return [
    new RateLimited('emails'),
];
}

Rate limiting prevents overload.


Delaying Jobs

You can delay job execution.

SendEmailJob::dispatch($user)->delay(now()->addMinutes(5));

Useful for follow-up notifications.


Specifying Queue Names

Jobs can be assigned to specific queues.

SendEmailJob::dispatch($user)->onQueue('emails');

Queue workers can listen to certain queues only.


Prioritizing Queues

You can process high-priority queues first:

php artisan queue:work --queue=high,default

Using the Queueable Trait for Customization

The Queueable trait gives extra configuration:

$this->queue = 'emails';
$this->delay = now()->addMinutes(2);
$this->tries = 5;

Retrying Failed Jobs

Laravel retries jobs automatically.

Configure retries:

php artisan queue:work --tries=5

Handling Job Failures

Failed jobs are stored in the failed_jobs table.

Create the table:

php artisan queue:failed-table
php artisan migrate

Inspecting Failed Jobs

List failures:

php artisan queue:failed

Retry a failed job:

php artisan queue:retry {id}

Retry all:

php artisan queue:retry all

Delete failed job:

php artisan queue:forget {id}

Handling Failure Inside a Job

You can define failed():

public function failed(Exception $exception)
{
Log::error($exception->getMessage());
}

Job Timeouts

Configure timeouts:

public $timeout = 120;

Or in queue worker:

php artisan queue:work --timeout=60

Job Batching

Batching allows grouping multiple jobs.

Create a batch:

Bus::batch([
new SendEmailJob($user1),
new SendEmailJob($user2),
])->dispatch();

You can track:

  • progress
  • completion
  • failure

Handling Batch Completion

->then(function (Batch $batch) {
// batch finished
})

Handling Batch Errors

->catch(function (Batch $batch, Throwable $e) {
// error handling
})

Job Chains

Chaining ensures jobs run in sequence:

SendEmailJob::withChain([
new LogEmailJob(),
new UpdateUserStatusJob(),
])->dispatch($user);

Chain ensures tasks execute in order.


Synchronous Dispatching for Testing

SendEmailJob::dispatchSync($user);

Runs job immediately and synchronously.


Dispatching Jobs After Database Commit

Sometimes you want to wait for a transaction.

SendEmailJob::dispatchAfterResponse($user);

Or:

SendEmailJob::dispatch($user)->afterCommit();

Ensures job runs only when transaction succeeds.


Serialization of Models in Jobs

Jobs use SerializesModels trait.

It serializes model IDs only, avoiding entire model serialization.

Example:

public function __construct(User $user)
{
$this->user = $user;
}

The model is rehydrated when job runs.


Avoiding Serialization Issues

Closures cannot be serialized.

Never pass:

dispatch(function () {
//
});

Unless using closure-based queueing.


Queued Event Listeners vs Jobs

Jobs are general-purpose tasks. Event listeners are event-driven.

Listeners implementing ShouldQueue are jobs triggered by events.

class SendEmailListener implements ShouldQueue

Jobs vs Notifications

Notifications can be queued automatically if the notification class uses ShouldQueue.

Jobs are more flexible and low-level.


Jobs vs Commands

Commands are for console operations. Jobs run in the queue worker.

They serve different use cases.


Using Queued Mail

Mailables can be queued without creating a job:

Mail::to($user)->queue(new WelcomeMail());

Using Queued Notifications

Similar:

$user->notify(new WelcomeNotification());

Queued Model Events

You can queue event listeners for:

  • created
  • updated
  • deleted

Example:

protected $dispatchesEvents = [
'created' => SendEmailJob::class,
];

Using Dispatch Helper Globally

dispatch(new SendEmailJob($user));

Scheduling Jobs

Using Task Scheduler:

$schedule->job(new CleanupJob())->daily();

Queuing Jobs on Specific Connection

SendEmailJob::dispatch($user)->onConnection('redis');

Database Queue Driver

Database driver stores jobs in the jobs table.

Create table:

php artisan queue:table
php artisan migrate

Redis Queue Driver

Redis is ideal for large-scale apps.

Configure in .env:

QUEUE_CONNECTION=redis

Laravel Horizon for Queue Monitoring

Horizon provides:

  • real-time metrics
  • failed job logs
  • throughput tracking
  • dashboard

Install Horizon for Redis-based queues:

composer require laravel/horizon

Returning Values From Jobs

Jobs should not return values because workers run asynchronously.

Instead, store results in:

  • DB
  • caches
  • logs

Avoiding Duplicate Job Execution

Use middleware:

new WithoutOverlapping($user->id)

Avoiding Job Overload

Use rate limiting middleware:

new RateLimited('emails')

Writing Idempotent Jobs

Idempotent jobs avoid duplication issues.

For example:

  • Check if email was already sent
  • Use unique constraints
  • Locking

Logging Inside Jobs

Log::info('Job started', ['user' => $this->user->id]);

Testing Queued Jobs

Fake the queue:

Queue::fake();

Dispatch:

SendEmailJob::dispatch($user);

Assert:

Queue::assertPushed(SendEmailJob::class);

Example Use Case: Sending Welcome Emails

class SendWelcomeEmail implements ShouldQueue
{
public function handle()
{
    Mail::to($this->user->email)
        ->send(new WelcomeMail($this->user));
}
}

Dispatch:

SendWelcomeEmail::dispatch($user);

Example Use Case: Upload Processing

class ProcessUpload implements ShouldQueue
{
public function handle()
{
    // image compression
    // video encoding
    // large file processing
}
}

Example Use Case: Third-Party API Sync

class SyncWithCRM implements ShouldQueue
{
public function handle()
{
    Http::post('https://crm.com/api', $this->data);
}
}

Best Practices for Jobs in Laravel

  • Keep jobs small and focused
  • Avoid heavy logic inside controllers; use jobs instead
  • Use queues for long-running tasks
  • Use redis for high-performance queues
  • Use Horizon for monitoring large queue systems
  • Make jobs idempotent to avoid duplication
  • Avoid passing complex objects; use IDs
  • Use middleware for rate limiting and overlapping prevention
  • Store results in database or cache
  • Always handle failures
  • Use batches for grouped processing
  • Use chains for dependent tasks
  • Use dispatchAfterResponse when necessary

Example of a Fully Configured Job

class SendEmailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $timeout = 60;
public function __construct(public User $user)
{}
public function handle()
{
    Mail::to($this->user->email)->send(new WelcomeMail());
}
public function failed(Throwable $e)
{
    Log::error('Send email failed', [
        'user_id' => $this->user->id,
        'error' => $e->getMessage()
    ]);
}
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *